mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-10 05:27:01 +03:00
Add MCP23s17 + further optimizations
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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 void pca9535_set_direction(gpio_exp_t* self);
|
||||||
static int pca9535_read(gpio_exp_t* self);
|
static int pca9535_read(gpio_exp_t* self);
|
||||||
static void pca9535_write(gpio_exp_t* self);
|
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);
|
||||||
static int pca85xx_read(gpio_exp_t* self);
|
static int pca85xx_read(gpio_exp_t* self);
|
||||||
static void pca85xx_write(gpio_exp_t* self);
|
static void pca85xx_write(gpio_exp_t* self);
|
||||||
|
|
||||||
static void mcp23017_init(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_pull_mode(gpio_exp_t* self);
|
||||||
static void mcp23017_set_direction(gpio_exp_t* self);
|
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 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 esp_err_t i2c_write(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len);
|
||||||
static uint16_t i2c_read(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, bool word);
|
static uint32_t i2c_read(uint8_t port, uint8_t addr, uint8_t reg, int len);
|
||||||
static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data);
|
|
||||||
|
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);
|
esp_err_t (*init)(gpio_exp_t* self);
|
||||||
int (*read)(gpio_exp_t* self);
|
int (*read)(gpio_exp_t* self);
|
||||||
void (*write)(gpio_exp_t* self);
|
void (*write)(gpio_exp_t* self);
|
||||||
void (*set_direction)(gpio_exp_t* self);
|
void (*set_direction)(gpio_exp_t* self);
|
||||||
void (*set_pull_mode)(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,87 +392,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************************
|
|
||||||
* Find the expander related to base
|
|
||||||
*/
|
|
||||||
static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) {
|
|
||||||
// a mutex would be better, but risk is so small...
|
|
||||||
for (int i = 0; !expander && i < n_expanders; i++) {
|
|
||||||
if (*gpio >= expanders[i].first && *gpio <= expanders[i].last) expander = expanders + i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize GPIO number
|
|
||||||
if (expander && *gpio >= expander->first) *gpio -= expander->first;
|
|
||||||
|
|
||||||
return expander;
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************************************************************
|
|
||||||
* PCA9535 family : direction, read and write
|
|
||||||
*/
|
|
||||||
static void pca9535_set_direction(gpio_exp_t* self) {
|
|
||||||
i2c_write_word(self->phy.port, self->phy.addr, 0x06, self->r_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pca9535_read(gpio_exp_t* self) {
|
|
||||||
return i2c_read(self->phy.port, self->phy.addr, 0x00, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pca9535_write(gpio_exp_t* self) {
|
|
||||||
i2c_write_word(self->phy.port, self->phy.addr, 0x02, self->shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************************************************************
|
|
||||||
* PCA85xx family : read and write
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
i2c_write_word(self->phy.port, self->phy.addr, 0xff, self->r_mask | self->w_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pca85xx_read(gpio_exp_t* self) {
|
|
||||||
return i2c_read(self->phy.port, self->phy.addr, 0xff, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pca85xx_write(gpio_exp_t* self) {
|
|
||||||
// all input must be set to 1 (open drain)
|
|
||||||
i2c_write_word(self->phy.port, self->phy.addr, 0xff, self->shadow | self->r_mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************************************************************
|
|
||||||
* MCP23017 family : init, direction, read and write
|
|
||||||
*/
|
|
||||||
static void mcp23017_init(gpio_exp_t* self) {
|
|
||||||
/*
|
|
||||||
0111 x10x = same bank, mirrot single int, no sequentµial, open drain, active low
|
|
||||||
not sure about this funny change of mapping of the control register itself, really?
|
|
||||||
*/
|
|
||||||
i2c_write_byte(self->phy.port, self->phy.addr, 0x05, 0x74);
|
|
||||||
i2c_write_byte(self->phy.port, self->phy.addr, 0x0a, 0x74);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mcp23017_set_direction(gpio_exp_t* self) {
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mcp23017_set_pull_mode(gpio_exp_t* self) {
|
|
||||||
i2c_write_word(self->phy.port, self->phy.addr, 0x0c, self->pullup);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mcp23017_write(gpio_exp_t* self) {
|
|
||||||
i2c_write_word(self->phy.port, self->phy.addr, 0x12, self->shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* INTR low-level handler
|
* INTR low-level handler
|
||||||
@@ -520,76 +459,152 @@ void service_handler(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
*
|
* Find the expander related to base
|
||||||
*/
|
*/
|
||||||
static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val) {
|
static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) {
|
||||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
// a mutex would be better, but risk is so small...
|
||||||
i2c_master_start(cmd);
|
for (int i = 0; !expander && i < n_expanders; i++) {
|
||||||
|
if (*gpio >= expanders[i].first && *gpio <= expanders[i].last) expander = expanders + i;
|
||||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
|
||||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
|
||||||
i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
|
|
||||||
|
|
||||||
i2c_master_stop(cmd);
|
|
||||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
|
||||||
i2c_cmd_link_delete(cmd);
|
|
||||||
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "I2C write failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
// normalize GPIO number
|
||||||
|
if (expander && *gpio >= expander->first) *gpio -= expander->first;
|
||||||
|
|
||||||
|
return expander;
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* I2C read 8 or 16 bits word
|
DRIVERS
|
||||||
|
****************************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* PCA9535 family : direction, read and write
|
||||||
*/
|
*/
|
||||||
static uint16_t i2c_read(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, bool word) {
|
static void pca9535_set_direction(gpio_exp_t* self) {
|
||||||
uint8_t data[2];
|
i2c_write(self->phy.port, self->phy.addr, 0x06, self->r_mask, 2);
|
||||||
|
}
|
||||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
|
||||||
|
|
||||||
i2c_master_start(cmd);
|
static int pca9535_read(gpio_exp_t* self) {
|
||||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
return i2c_read(self->phy.port, self->phy.addr, 0x00, 2);
|
||||||
|
}
|
||||||
|
|
||||||
// when using a register, write it's value then the device address again
|
static void pca9535_write(gpio_exp_t* self) {
|
||||||
if (reg != 0xff) {
|
i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (word) {
|
|
||||||
i2c_master_read_byte(cmd, data, I2C_MASTER_ACK);
|
|
||||||
i2c_master_read_byte(cmd, data + 1, I2C_MASTER_NACK);
|
|
||||||
} else {
|
|
||||||
i2c_master_read_byte(cmd, data, I2C_MASTER_NACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
i2c_master_stop(cmd);
|
|
||||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
|
||||||
i2c_cmd_link_delete(cmd);
|
|
||||||
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "I2C read failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return *(uint16_t*) data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* I2C write 16 bits word
|
* PCA85xx family : read and write
|
||||||
*/
|
*/
|
||||||
static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data) {
|
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
|
||||||
|
i2c_write(self->phy.port, self->phy.addr, 0xff, self->r_mask | self->w_mask, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pca85xx_read(gpio_exp_t* self) {
|
||||||
|
return i2c_read(self->phy.port, self->phy.addr, 0xff, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pca85xx_write(gpio_exp_t* self) {
|
||||||
|
// all input must be set to 1 (open drain)
|
||||||
|
i2c_write(self->phy.port, self->phy.addr, 0xff, self->shadow | self->r_mask, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* MCP23017 family : init, direction, read and write
|
||||||
|
*/
|
||||||
|
static esp_err_t mcp23017_init(gpio_exp_t* self) {
|
||||||
|
/*
|
||||||
|
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 = 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
|
||||||
|
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) {
|
||||||
|
// default to input and set real input to generate interrupt
|
||||||
|
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) {
|
||||||
|
i2c_write(self->phy.port, self->phy.addr, 0x0c, self->pullup, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mcp23017_read(gpio_exp_t* self) {
|
||||||
|
// 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) {
|
||||||
|
i2c_write(self->phy.port, self->phy.addr, 0x12, self->shadow, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* MCP23s17 family : init, direction, read and write
|
||||||
|
*/
|
||||||
|
static esp_err_t mcp23s17_init(gpio_exp_t* self) {
|
||||||
|
self->spi_handle = spi_config(&self->phy);
|
||||||
|
|
||||||
|
/*
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcp23s17_set_pull_mode(gpio_exp_t* self) {
|
||||||
|
spi_write(self->spi_handle, self->phy.addr, 0x0c, self->pullup, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len) {
|
||||||
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);
|
||||||
if (reg != 0xff) i2c_master_write_byte(cmd, reg, 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);
|
|
||||||
|
// 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) {
|
||||||
@@ -597,4 +612,99 @@ static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* I2C read up to 32 bits
|
||||||
|
*/
|
||||||
|
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_master_start(cmd);
|
||||||
|
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
|
||||||
|
if (reg != 0xff) {
|
||||||
|
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||||
|
i2c_master_start(cmd);
|
||||||
|
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);
|
||||||
|
esp_err_t ret = i2c_master_cmd_begin(port, cmd, 100 / portTICK_RATE_MS);
|
||||||
|
i2c_cmd_link_delete(cmd);
|
||||||
|
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "I2C read failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************************
|
||||||
|
SPI low level
|
||||||
|
***************************************************************************************/
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* SPI device addition
|
||||||
|
*/
|
||||||
|
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 );
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -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 gpio_exp_phy_s {
|
||||||
struct {
|
uint8_t addr;
|
||||||
uint8_t addr, port;
|
struct { // for I2C
|
||||||
|
uint8_t port;
|
||||||
};
|
};
|
||||||
struct {
|
struct { // for SPI
|
||||||
uint8_t cs_pin;
|
uint32_t speed;
|
||||||
|
uint8_t host;
|
||||||
|
uint8_t cs_pin;
|
||||||
};
|
};
|
||||||
} phy;
|
} phy;
|
||||||
} gpio_exp_config_t;
|
} gpio_exp_config_t;
|
||||||
|
|||||||
@@ -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_pad_select_gpio(gpio);
|
||||||
gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
|
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
|
||||||
gpio_set_level_x(gpio, 1);
|
gpio_set_level(gpio, 1);
|
||||||
} else if (!strcasecmp(value, "gnd")) {
|
} else if (!strcasecmp(value, "gnd")) {
|
||||||
gpio_pad_select_gpio_x(gpio);
|
gpio_pad_select_gpio(gpio);
|
||||||
gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
|
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
|
||||||
gpio_set_level_x(gpio, 0);
|
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,
|
||||||
|
|||||||
Reference in New Issue
Block a user