Browse Source

Add MCP23s17 + further optimizations

Philippe G 3 years ago
parent
commit
5ac153f808

+ 3 - 1
components/services/accessors.c

@@ -491,7 +491,7 @@ const gpio_exp_config_t* config_gpio_exp_get(int index) {
 
 
 	// re-initialize config every time
 	// re-initialize config every time
 	memset(&config, 0, sizeof(config));
 	memset(&config, 0, sizeof(config));
-	config.intr = -1; config.count = 16; config.base = GPIO_NUM_MAX; config.phy.port = i2c_system_port;
+	config.intr = -1; config.count = 16; config.base = GPIO_NUM_MAX; config.phy.port = i2c_system_port; config.phy.host = spi_system_host;
 
 
 	nvs_item = config_alloc_get(NVS_TYPE_STR, "gpio_exp_config");
 	nvs_item = config_alloc_get(NVS_TYPE_STR, "gpio_exp_config");
 	if (!nvs_item || !*nvs_item) return NULL;
 	if (!nvs_item || !*nvs_item) return NULL;
@@ -505,6 +505,8 @@ const gpio_exp_config_t* config_gpio_exp_get(int index) {
 	}
 	}
 
 
 	if ((p = strcasestr(item, "addr")) != NULL) config.phy.addr = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(item, "addr")) != NULL) config.phy.addr = atoi(strchr(p, '=') + 1);
+	if ((p = strcasestr(item, "cs_pin")) != NULL) config.phy.cs_pin = atoi(strchr(p, '=') + 1);
+	if ((p = strcasestr(item, "speed")) != NULL) config.phy.speed = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(item, "intr")) != NULL) config.intr = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(item, "intr")) != NULL) config.intr = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(item, "base")) != NULL) config.base = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(item, "base")) != NULL) config.base = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(item, "count")) != NULL) config.count = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(item, "count")) != NULL) config.count = atoi(strchr(p, '=') + 1);

+ 241 - 131
components/services/gpio_exp.c

@@ -16,6 +16,7 @@
 #include "esp_log.h"
 #include "esp_log.h"
 #include "driver/gpio.h"
 #include "driver/gpio.h"
 #include "driver/i2c.h"
 #include "driver/i2c.h"
+#include "driver/spi_master.h"
 #include "gpio_exp.h"
 #include "gpio_exp.h"
 
 
 #define GPIO_EXP_INTR	0x100
 #define GPIO_EXP_INTR	0x100
@@ -28,7 +29,10 @@
  
  
 typedef struct gpio_exp_s {
 typedef struct gpio_exp_s {
 	uint32_t first, last;
 	uint32_t first, last;
-	union gpio_exp_phy_u phy;
+	struct  {
+		struct gpio_exp_phy_s phy;
+		spi_device_handle_t spi_handle;
+	};
 	uint32_t shadow, pending;
 	uint32_t shadow, pending;
 	TickType_t age;
 	TickType_t age;
 	SemaphoreHandle_t mutex;
 	SemaphoreHandle_t mutex;
@@ -54,35 +58,44 @@ static const char TAG[] = "gpio expander";
 static void   IRAM_ATTR intr_isr_handler(void* arg);
 static void   IRAM_ATTR intr_isr_handler(void* arg);
 static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio);
 static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio);
 
 
-static void   pca9535_set_direction(gpio_exp_t* self);
-static int    pca9535_read(gpio_exp_t* self);
-static void   pca9535_write(gpio_exp_t* self);
+static void pca9535_set_direction(gpio_exp_t* self);
+static int  pca9535_read(gpio_exp_t* self);
+static void pca9535_write(gpio_exp_t* self);
 
 
-static void   pca85xx_set_direction(gpio_exp_t* self);
-static int    pca85xx_read(gpio_exp_t* self);
-static void   pca85xx_write(gpio_exp_t* self);
+static void pca85xx_set_direction(gpio_exp_t* self);
+static int  pca85xx_read(gpio_exp_t* self);
+static void pca85xx_write(gpio_exp_t* self);
 
 
-static void   mcp23017_init(gpio_exp_t* self);
-static void   mcp23017_set_pull_mode(gpio_exp_t* self);
-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 esp_err_t mcp23017_init(gpio_exp_t* self);
+static void      mcp23017_set_pull_mode(gpio_exp_t* self);
+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 esp_err_t mcp23s17_init(gpio_exp_t* self);
+static void      mcp23s17_set_pull_mode(gpio_exp_t* self);
+static void      mcp23s17_set_direction(gpio_exp_t* self);
+static int       mcp23s17_read(gpio_exp_t* self);
+static void      mcp23s17_write(gpio_exp_t* self);
 
 
 static void   service_handler(void *arg);
 static void   service_handler(void *arg);
 static void   debounce_handler( TimerHandle_t xTimer );
 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 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(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len);
+static uint32_t  i2c_read(uint8_t port, uint8_t addr, uint8_t reg, int len);
+
+static spi_device_handle_t spi_config(struct gpio_exp_phy_s *phy);
+static esp_err_t           spi_write(spi_device_handle_t handle, uint8_t addr, uint8_t reg, uint32_t data, int len);
+static uint32_t            spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg, int len);
 
 
 static const struct gpio_exp_model_s {
 static const struct gpio_exp_model_s {
 	char *model;
 	char *model;
 	gpio_int_type_t trigger;
 	gpio_int_type_t trigger;
-	void (*init)(gpio_exp_t* self);
-	int  (*read)(gpio_exp_t* self);
-	void (*write)(gpio_exp_t* self);
-	void (*set_direction)(gpio_exp_t* self);
-	void (*set_pull_mode)(gpio_exp_t* self);
+	esp_err_t (*init)(gpio_exp_t* self);
+	int       (*read)(gpio_exp_t* self);
+	void      (*write)(gpio_exp_t* self);
+	void      (*set_direction)(gpio_exp_t* self);
+	void      (*set_pull_mode)(gpio_exp_t* self);
 } registered[] = {
 } registered[] = {
 	{ .model = "pca9535",
 	{ .model = "pca9535",
 	  .trigger = GPIO_INTR_NEGEDGE, 
 	  .trigger = GPIO_INTR_NEGEDGE, 
@@ -101,6 +114,13 @@ static const struct gpio_exp_model_s {
 	  .set_pull_mode = mcp23017_set_pull_mode,
 	  .set_pull_mode = mcp23017_set_pull_mode,
 	  .read = mcp23017_read,
 	  .read = mcp23017_read,
 	  .write = mcp23017_write, },
 	  .write = mcp23017_write, },
+	{ .model = "mcp23s17",
+	  .trigger = GPIO_INTR_NEGEDGE, 
+	  .init = mcp23s17_init,
+	  .set_direction = mcp23s17_set_direction,
+	  .set_pull_mode = mcp23s17_set_pull_mode,
+	  .read = mcp23s17_read,
+	  .write = mcp23s17_write, },
 };
 };
 
 
 static EXT_RAM_ATTR uint8_t n_expanders;
 static EXT_RAM_ATTR uint8_t n_expanders;
@@ -150,7 +170,7 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
 	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(struct gpio_exp_phy_s));
 	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)
@@ -372,6 +392,72 @@ esp_err_t gpio_isr_handler_remove_x(int gpio) {
 	return gpio_exp_isr_handler_remove(gpio, NULL);
 	return gpio_exp_isr_handler_remove(gpio, NULL);
 }
 }
 
 
+
+/****************************************************************************************
+ * INTR low-level handler
+ */
+static void IRAM_ATTR intr_isr_handler(void* arg) {
+	BaseType_t woken = pdFALSE;
+	
+	xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken);
+	if (woken) portYIELD_FROM_ISR();
+
+	ESP_EARLY_LOGD(TAG, "INTR for expander base %d", gpio_exp_get_base(arg));
+}
+
+/****************************************************************************************
+ * INTR debounce handler
+ */
+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) {
+		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);
+				}
+			}
+		}
+
+		// 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);  
+		}
+	}
+}
+
 /****************************************************************************************
 /****************************************************************************************
  * Find the expander related to base
  * Find the expander related to base
  */
  */
@@ -387,19 +473,23 @@ static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) {
 	return expander;
 	return expander;
 }
 }
 
 
+/****************************************************************************************
+                                        DRIVERS                                       
+****************************************************************************************/
+
 /****************************************************************************************
 /****************************************************************************************
  * PCA9535 family : direction, read and write
  * PCA9535 family : direction, read and write
  */
  */
 static void pca9535_set_direction(gpio_exp_t* self) {
 static void pca9535_set_direction(gpio_exp_t* self) {
-	i2c_write_word(self->phy.port, self->phy.addr, 0x06, self->r_mask);
+	i2c_write(self->phy.port, self->phy.addr, 0x06, self->r_mask, 2);
 }
 }
 
 
 static int pca9535_read(gpio_exp_t* self) {
 static int pca9535_read(gpio_exp_t* self) {
-	return i2c_read(self->phy.port, self->phy.addr, 0x00, true);
+	return i2c_read(self->phy.port, self->phy.addr, 0x00, 2);
 }
 }
 
 
 static void pca9535_write(gpio_exp_t* self) {
 static void pca9535_write(gpio_exp_t* self) {
-	i2c_write_word(self->phy.port, self->phy.addr, 0x02, self->shadow);
+	i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
@@ -407,134 +497,117 @@ static void pca9535_write(gpio_exp_t* self) {
  */
  */
 static void pca85xx_set_direction(gpio_exp_t* self) {
 static void pca85xx_set_direction(gpio_exp_t* self) {
 	// all inputs must be set to 1 (open drain) and output are left open as well
 	// all inputs must be set to 1 (open drain) and output are left open as well
-	i2c_write_word(self->phy.port, self->phy.addr, 0xff, self->r_mask | self->w_mask);
+	i2c_write(self->phy.port, self->phy.addr, 0xff, self->r_mask | self->w_mask, true);
 }
 }
 
 
 static int pca85xx_read(gpio_exp_t* self) {
 static int pca85xx_read(gpio_exp_t* self) {
-	return i2c_read(self->phy.port, self->phy.addr, 0xff, true);
+	return i2c_read(self->phy.port, self->phy.addr, 0xff, 2);
 }
 }
 
 
 static void pca85xx_write(gpio_exp_t* self) {
 static void pca85xx_write(gpio_exp_t* self) {
 	// all input must be set to 1 (open drain)
 	// all input must be set to 1 (open drain)
-	i2c_write_word(self->phy.port, self->phy.addr, 0xff, self->shadow | self->r_mask);
+	i2c_write(self->phy.port, self->phy.addr, 0xff, self->shadow | self->r_mask, true);
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
  * MCP23017 family : init, direction, read and write
  * MCP23017 family : init, direction, read and write
  */
  */
-static void mcp23017_init(gpio_exp_t* self) {
+static esp_err_t 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, 0x0a, 0x74);
+	esp_err_t err = i2c_write(self->phy.port, self->phy.addr, 0x05, 0x74, 1);
+	err |= i2c_write(self->phy.port, self->phy.addr, 0x0a, 0x74, 1);
 
 
 	// no interrupt on comparison or on change
 	// no interrupt on comparison or on change
-	i2c_write_word(self->phy.port, self->phy.addr, 0x04, 0x00);
-	i2c_write_word(self->phy.port, self->phy.addr, 0x08, 0x00);
+	err |= i2c_write(self->phy.port, self->phy.addr, 0x04, 0x00, 2);
+	err |= i2c_write(self->phy.port, self->phy.addr, 0x08, 0x00, 2);
+
+	return err;
 }
 }
 
 
 static void mcp23017_set_direction(gpio_exp_t* self) {
 static void mcp23017_set_direction(gpio_exp_t* self) {
 	// default to input and set real input to generate interrupt
 	// default to input and set real input to generate interrupt
-	i2c_write_word(self->phy.port, self->phy.addr, 0x00, ~self->w_mask);
-	i2c_write_word(self->phy.port, self->phy.addr, 0x04, self->r_mask);
+	i2c_write(self->phy.port, self->phy.addr, 0x00, ~self->w_mask, 2);
+	i2c_write(self->phy.port, self->phy.addr, 0x04, self->r_mask, 2);
 }
 }
 
 
 static void mcp23017_set_pull_mode(gpio_exp_t* self) {
 static void mcp23017_set_pull_mode(gpio_exp_t* self) {
-	i2c_write_word(self->phy.port, self->phy.addr, 0x0c, self->pullup);
+	i2c_write(self->phy.port, self->phy.addr, 0x0c, self->pullup, 2);
 }
 }
 
 
 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
-	return i2c_read(self->phy.port, self->phy.addr, 0x12, true);
+	// read the pins value, not the stored one @interrupt
+	return i2c_read(self->phy.port, self->phy.addr, 0x12, 2);
 }
 }
 
 
 static void mcp23017_write(gpio_exp_t* self) {
 static void mcp23017_write(gpio_exp_t* self) {
-	i2c_write_word(self->phy.port, self->phy.addr, 0x12, self->shadow);
+	i2c_write(self->phy.port, self->phy.addr, 0x12, self->shadow, 2);
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
- * INTR low-level handler
+ * MCP23s17 family : init, direction, read and write
  */
  */
-static void IRAM_ATTR intr_isr_handler(void* arg) {
-	BaseType_t woken = pdFALSE;
+static esp_err_t mcp23s17_init(gpio_exp_t* self) {
+	self->spi_handle = spi_config(&self->phy);
 	
 	
-	xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken);
-	if (woken) portYIELD_FROM_ISR();
+	/*
+	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?
+	*/
+	esp_err_t err = spi_write(self->spi_handle, self->phy.addr, 0x05, 0x74, 1);
+	err |= spi_write(self->spi_handle, self->phy.addr, 0x0a, 0x74, 1);
 
 
-	ESP_EARLY_LOGD(TAG, "INTR for expander base %d", gpio_exp_get_base(arg));
-}
+	// no interrupt on comparison or on change
+	err |= spi_write(self->spi_handle, self->phy.addr, 0x04, 0x00, 2);
+	err |= spi_write(self->spi_handle, self->phy.addr, 0x08, 0x00, 2);
 
 
-/****************************************************************************************
- * INTR debounce handler
- */
-static void debounce_handler( TimerHandle_t xTimer ) {
-	struct gpio_exp_isr_s *isr = (struct gpio_exp_isr_s*) pvTimerGetTimerID (xTimer);
-	isr->handler(isr->arg);
+	return err;
 }
 }
 
 
-/****************************************************************************************
- * 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));
+static void mcp23s17_set_direction(gpio_exp_t* self) {
+	// default to input and set real input to generate interrupt
+	spi_write(self->spi_handle, self->phy.addr, 0x00, ~self->w_mask, 2);
+	spi_write(self->spi_handle, self->phy.addr, 0x04, self->r_mask, 2);
+}
 
 
-				// 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();
+static void mcp23s17_set_pull_mode(gpio_exp_t* self) {
+	spi_write(self->spi_handle, self->phy.addr, 0x0c, self->pullup, 2);
+}
 
 
-				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);
-				}
-			}
-		}
+static int mcp23s17_read(gpio_exp_t* self) {
+	// read the pins value, not the stored one @interrupt
+	return spi_read(self->spi_handle, self->phy.addr, 0x12, 2);
+}
 
 
-		// 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);  
-		}
-	}
+static void mcp23s17_write(gpio_exp_t* self) {
+	spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2);
 }
 }
 
 
+/***************************************************************************************
+                                     I2C low level                                   
+***************************************************************************************/
+
 /****************************************************************************************
 /****************************************************************************************
- * 
+ * I2C write up to 32 bits
  */
  */
-static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val) {
-    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+static esp_err_t i2c_write(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len) {
+	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, reg, I2C_MASTER_NACK);
-	i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
-	
+	i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
+	if (reg != 0xff) i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
+
+	// works with out endianness
+	if (len > 1) i2c_master_write(cmd, (uint8_t*) &data, len, I2C_MASTER_NACK);
+	else i2c_master_write_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(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");
 	}
 	}
 	
 	
@@ -542,59 +615,96 @@ static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg,
 }
 }
 
 
 /****************************************************************************************
 /****************************************************************************************
- * I2C read 8 or 16 bits word
+ * I2C read up to 32 bits
  */
  */
-static uint16_t i2c_read(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, bool word) {
-	uint8_t data[2];
+static uint32_t i2c_read(uint8_t port, uint8_t addr, uint8_t reg, int len) {
+	uint32_t data = 0;
 	
 	
 	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
 	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, (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
 	if (reg != 0xff) {
 	if (reg != 0xff) {
 		i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
 		i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
 		i2c_master_start(cmd);
 		i2c_master_start(cmd);
-		i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, 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_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
 	}
 	}
 	
 	
+	// works with out endianness
+	if (len > 1) i2c_master_read(cmd, (uint8_t*) &data, len, I2C_MASTER_LAST_NACK);
+	else i2c_master_read_byte(cmd, (uint8_t*) &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(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 read failed");
 		ESP_LOGW(TAG, "I2C read failed");
 	}
 	}
 
 
-	return *(uint16_t*) data;
+	return data;
 }
 }
 
 
+/***************************************************************************************
+                                     SPI low level                                   
+***************************************************************************************/
+
 /****************************************************************************************
 /****************************************************************************************
- * I2C write 16 bits word
+ * SPI device addition
  */
  */
-static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data) {
-	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
-    i2c_master_start(cmd);
-	
-	i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
-	if (reg != 0xff) i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
-	i2c_master_write(cmd, (uint8_t*) &data, 2, I2C_MASTER_NACK);
-    
-	i2c_master_stop(cmd);
-	esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
-    i2c_cmd_link_delete(cmd);
-	
-	if (ret != ESP_OK) {		
-		ESP_LOGW(TAG, "I2C write failed");
-	}
+static spi_device_handle_t spi_config(struct gpio_exp_phy_s *phy) {
+    spi_device_interface_config_t config;
+    spi_device_handle_t handle = NULL;
+
+	// initialize ChipSelect (CS)
+	gpio_set_direction(phy->cs_pin, GPIO_MODE_OUTPUT );
+	gpio_set_level(phy->cs_pin, 0 );
 	
 	
-    return ret;
+    memset( &config, 0, sizeof( spi_device_interface_config_t ) );
+
+	config.command_bits = config.address_bits = 8;
+    config.clock_speed_hz = phy->speed ? phy->speed : SPI_MASTER_FREQ_8M;
+    config.spics_io_num = phy->cs_pin;
+    config.queue_size = 1;
+	config.flags = SPI_DEVICE_NO_DUMMY;
+
+    spi_bus_add_device( phy->host, &config, &handle );
+
+	return handle;
+}
+
+/****************************************************************************************
+ * SPI write up to 32 bits
+ */
+static esp_err_t spi_write(spi_device_handle_t handle, uint8_t addr, uint8_t reg, uint32_t data, int len) {
+    spi_transaction_t transaction = { };
+
+	// rx_buffer is NULL, nothing to receive
+	transaction.flags = SPI_TRANS_USE_TXDATA;
+	transaction.cmd = addr << 1;
+	transaction.addr = reg;
+	transaction.tx_data[1] = data; transaction.tx_data[2] = data >> 8;
+	transaction.length = len * 8;
+
+	// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)		
+	return spi_device_polling_transmit(handle, &transaction);
+}
+
+/****************************************************************************************
+ * SPI read up to 32 bits
+ */
+static uint32_t spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg, int len) {
+	spi_transaction_t transaction = { };
+
+	// tx_buffer is NULL, nothing to transmit except cmd/addr
+	transaction.flags = SPI_TRANS_USE_RXDATA;
+	transaction.cmd = (addr << 1) | 1;
+	transaction.addr = reg;
+	transaction.length = len * 8;
+
+	// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)		
+	spi_device_polling_transmit(handle, &transaction);
+	return *(uint32_t*) transaction.rx_data;
 }
 }

+ 8 - 5
components/services/gpio_exp.h

@@ -19,12 +19,15 @@ typedef struct {
 	uint8_t intr;
 	uint8_t intr;
 	uint8_t count;
 	uint8_t count;
 	uint32_t base;
 	uint32_t base;
-	union gpio_exp_phy_u {
-		struct {
-			uint8_t addr, port;
+	struct gpio_exp_phy_s {
+		uint8_t addr;
+		struct {				// for I2C
+			uint8_t port;
 		};
 		};
-		struct {
-			uint8_t cs_pin;
+		struct {				// for SPI
+			uint32_t speed;	
+			uint8_t host;	
+			uint8_t cs_pin; 		
 		};
 		};
 	} phy;	
 	} phy;	
 } gpio_exp_config_t;
 } gpio_exp_config_t;

+ 37 - 14
components/services/services.c

@@ -39,20 +39,40 @@ static const char *TAG = "services";
 /****************************************************************************************
 /****************************************************************************************
  * 
  * 
  */
  */
-void set_power_gpio(int gpio, char *value) {
+void set_chip_power_gpio(int gpio, char *value) {
 	bool parsed = true;
 	bool parsed = true;
+
+	// we only parse on-chip GPIOs
+	if (gpio >= GPIO_NUM_MAX) return;
 	
 	
 	if (!strcasecmp(value, "vcc") ) {
 	if (!strcasecmp(value, "vcc") ) {
-		gpio_pad_select_gpio_x(gpio);
-		gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
-		gpio_set_level_x(gpio, 1);
+		gpio_pad_select_gpio(gpio);
+		gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
+		gpio_set_level(gpio, 1);
 	} else if (!strcasecmp(value, "gnd")) {
 	} else if (!strcasecmp(value, "gnd")) {
-		gpio_pad_select_gpio_x(gpio);
-		gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
-		gpio_set_level_x(gpio, 0);
+		gpio_pad_select_gpio(gpio);
+		gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
+		gpio_set_level(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);
+}	
+
+void set_exp_power_gpio(int gpio, char *value) {
+	bool parsed = true;
+
+	// we only parse on-chip GPIOs
+	if (gpio < GPIO_NUM_MAX) return;
+	
+	if (!strcasecmp(value, "vcc") ) {
+		gpio_exp_set_direction(gpio, GPIO_MODE_OUTPUT, NULL);
+		gpio_exp_set_level(gpio, 1, true, NULL);
+	} else if (!strcasecmp(value, "gnd")) {
+		gpio_exp_set_direction(gpio, GPIO_MODE_OUTPUT, NULL);
+		gpio_exp_set_level(gpio, 0, true, NULL);
+	} else parsed = false;
+	
+	if (parsed) ESP_LOGI(TAG, "set expanded GPIO %u to %s", gpio, value);
  }	
  }	
  
  
 
 
@@ -70,6 +90,9 @@ void services_init(void) {
 	}
 	}
 #endif
 #endif
 
 
+	// set potential power GPIO on chip first in case expanders are power using these
+	parse_set_GPIO(set_chip_power_gpio);
+
 	// shared I2C bus 
 	// shared I2C bus 
 	const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
 	const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
 	ESP_LOGI(TAG,"Configuring I2C sda:%d scl:%d port:%u speed:%u", i2c_config->sda_io_num, i2c_config->scl_io_num, i2c_system_port, i2c_config->master.clk_speed);
 	ESP_LOGI(TAG,"Configuring I2C sda:%d scl:%d port:%u speed:%u", i2c_config->sda_io_num, i2c_config->scl_io_num, i2c_system_port, i2c_config->master.clk_speed);
@@ -82,13 +105,6 @@ void services_init(void) {
 		ESP_LOGW(TAG, "no I2C configured");
 		ESP_LOGW(TAG, "no I2C configured");
 	}	
 	}	
 
 
-	// create GPIO expanders
-	const gpio_exp_config_t* gpio_exp_config;
-	for (int count = 0; (gpio_exp_config = config_gpio_exp_get(count)); count++) gpio_exp_create(gpio_exp_config);
-
-	// set potential power GPIO (a GPIO-powered expander might be an issue)
-	parse_set_GPIO(set_power_gpio);
-
 	const spi_bus_config_t * spi_config = config_spi_get((spi_host_device_t*) &spi_system_host);
 	const spi_bus_config_t * spi_config = config_spi_get((spi_host_device_t*) &spi_system_host);
 	ESP_LOGI(TAG,"Configuring SPI data:%d clk:%d host:%u dc:%d", spi_config->mosi_io_num, spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio);
 	ESP_LOGI(TAG,"Configuring SPI data:%d clk:%d host:%u dc:%d", spi_config->mosi_io_num, spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio);
 	
 	
@@ -105,6 +121,13 @@ void services_init(void) {
 		ESP_LOGW(TAG, "no SPI configured");
 		ESP_LOGW(TAG, "no SPI configured");
 	}	
 	}	
 
 
+	// create GPIO expanders
+	const gpio_exp_config_t* gpio_exp_config;
+	for (int count = 0; (gpio_exp_config = config_gpio_exp_get(count)); count++) gpio_exp_create(gpio_exp_config);
+
+	// now set potential power GPIO on expander 
+	parse_set_GPIO(set_exp_power_gpio);
+
 	// system-wide PWM timer configuration
 	// system-wide PWM timer configuration
 	ledc_timer_config_t pwm_timer = {
 	ledc_timer_config_t pwm_timer = {
 		.duty_resolution = LEDC_TIMER_13_BIT, 
 		.duty_resolution = LEDC_TIMER_13_BIT,