mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 04:57:06 +03:00
big merge
This commit is contained in:
@@ -29,12 +29,11 @@
|
||||
#include "driver/spi_common_internal.h"
|
||||
#include "esp32/rom/efuse.h"
|
||||
#include "adac.h"
|
||||
#include "trace.h"
|
||||
#include "tools.h"
|
||||
#include "monitor.h"
|
||||
#include "messaging.h"
|
||||
#include "network_ethernet.h"
|
||||
|
||||
|
||||
static const char *TAG = "services";
|
||||
const char *i2c_name_type="I2C";
|
||||
const char *spi_name_type="SPI";
|
||||
@@ -63,7 +62,6 @@ static char * config_spdif_get_string(){
|
||||
",ws=" STR(CONFIG_SPDIF_WS_IO) ",do=" STR(CONFIG_SPDIF_DO_IO));
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -110,9 +108,9 @@ bool is_spdif_config_locked(){
|
||||
static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
|
||||
char *p;
|
||||
pin_config->bck_io_num = pin_config->ws_io_num = pin_config->data_out_num = pin_config->data_in_num = -1;
|
||||
if ((p = strcasestr(config, "bck")) != NULL) pin_config->bck_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "ws")) != NULL) pin_config->ws_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "do")) != NULL) pin_config->data_out_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "bck"))) sscanf(p, "bck%*[^=]=%d", &pin_config->bck_io_num);
|
||||
if ((p = strcasestr(config, "ws"))) sscanf(p, "ws%*[^=]=%d", &pin_config->ws_io_num);
|
||||
if ((p = strcasestr(config, "do"))) sscanf(p, "do%*[^=]=%d", &pin_config->data_out_num);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -120,19 +118,20 @@ static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
|
||||
*/
|
||||
const i2s_platform_config_t * config_get_i2s_from_str(char * dac_config ){
|
||||
static EXT_RAM_ATTR i2s_platform_config_t i2s_dac_pin;
|
||||
memset(&i2s_dac_pin, 0xFF, sizeof(i2s_dac_pin));
|
||||
memset(&i2s_dac_pin, 0xff, sizeof(i2s_dac_pin));
|
||||
set_i2s_pin(dac_config, &i2s_dac_pin.pin);
|
||||
strcpy(i2s_dac_pin.model, "i2s");
|
||||
char * p=NULL;
|
||||
if ((p = strcasestr(dac_config, "i2c")) != NULL) i2s_dac_pin.i2c_addr = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(dac_config, "sda")) != NULL) i2s_dac_pin.sda = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(dac_config, "scl")) != NULL) i2s_dac_pin.scl = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(dac_config, "model")) != NULL) sscanf(p, "%*[^=]=%31[^,]", i2s_dac_pin.model);
|
||||
if ((p = strcasestr(dac_config, "mute")) != NULL) {
|
||||
|
||||
PARSE_PARAM(dac_config, "i2c", '=', i2s_dac_pin.i2c_addr);
|
||||
PARSE_PARAM(dac_config, "sda", '=', i2s_dac_pin.sda);
|
||||
PARSE_PARAM(dac_config, "scl", '=', i2s_dac_pin.scl);
|
||||
PARSE_PARAM_STR(dac_config, "model", '=', i2s_dac_pin.model, 31);
|
||||
if ((p = strcasestr(dac_config, "mute"))) {
|
||||
char mute[8] = "";
|
||||
sscanf(p, "%*[^=]=%7[^,]", mute);
|
||||
i2s_dac_pin.mute_gpio = atoi(mute);
|
||||
if ((p = strchr(mute, ':')) != NULL) i2s_dac_pin.mute_level = atoi(p + 1);
|
||||
PARSE_PARAM(p, "mute", ':', i2s_dac_pin.mute_level);
|
||||
}
|
||||
return &i2s_dac_pin;
|
||||
}
|
||||
@@ -140,52 +139,56 @@ const i2s_platform_config_t * config_get_i2s_from_str(char * dac_config ){
|
||||
/****************************************************************************************
|
||||
* Get eth config structure from config string
|
||||
*/
|
||||
const eth_config_t * config_get_eth_from_str(char * eth_config ){
|
||||
char * p=NULL;
|
||||
static EXT_RAM_ATTR eth_config_t eth_pin;
|
||||
memset(ð_pin, 0xFF, sizeof(eth_pin));
|
||||
memset(ð_pin.model, 0x00, sizeof(eth_pin.model));
|
||||
eth_pin.valid = true;
|
||||
const eth_config_t * config_get_eth_from_str(char* config ){
|
||||
static EXT_RAM_ATTR eth_config_t eth_config;
|
||||
memset(ð_config, 0xff, sizeof(eth_config));
|
||||
memset(ð_config.model, 0x00, sizeof(eth_config.model));
|
||||
eth_config.valid = true;
|
||||
|
||||
if ((p = strcasestr(eth_config, "model")) != NULL) sscanf(p, "%*[^=]=%15[^,]", eth_pin.model);
|
||||
if ((p = strcasestr(eth_config, "mdc")) != NULL) eth_pin.mdc = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "mdio")) != NULL) eth_pin.mdio = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "rst")) != NULL) eth_pin.rst = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "mosi")) != NULL) eth_pin.mosi = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "miso")) != NULL) eth_pin.miso = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "intr")) != NULL) eth_pin.intr = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "cs")) != NULL) eth_pin.cs = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "speed")) != NULL) eth_pin.speed = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "clk")) != NULL) eth_pin.clk = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(eth_config, "host")) != NULL) eth_pin.host = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM_STR(config, "model", '=', eth_config.model, 15);
|
||||
PARSE_PARAM(config, "mdc", '=', eth_config.mdc);
|
||||
PARSE_PARAM(config, "mdio", '=', eth_config.mdio);
|
||||
PARSE_PARAM(config, "rst", '=', eth_config.rst);
|
||||
PARSE_PARAM(config, "mosi", '=', eth_config.mosi);
|
||||
PARSE_PARAM(config, "miso", '=', eth_config.miso);
|
||||
PARSE_PARAM(config, "intr", '=', eth_config.intr);
|
||||
PARSE_PARAM(config, "cs", '=', eth_config.cs);
|
||||
PARSE_PARAM(config, "speed", '=', eth_config.speed);
|
||||
PARSE_PARAM(config, "clk", '=', eth_config.clk);
|
||||
|
||||
if(!eth_pin.model || strlen(eth_pin.model)==0){
|
||||
eth_pin.valid = false;
|
||||
return ð_pin;
|
||||
// only system host is available
|
||||
eth_config.host = spi_system_host;
|
||||
|
||||
if(!eth_config.model || strlen(eth_config.model)==0){
|
||||
eth_config.valid = false;
|
||||
return ð_config;
|
||||
}
|
||||
network_ethernet_driver_t* network_driver = network_ethernet_driver_autodetect(eth_pin.model);
|
||||
|
||||
network_ethernet_driver_t* network_driver = network_ethernet_driver_autodetect(eth_config.model);
|
||||
|
||||
if(!network_driver || !network_driver->valid){
|
||||
messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Ethernet config invalid: model %s %s",eth_pin.model,network_driver?"was not compiled in":"was not found");
|
||||
eth_pin.valid = false;
|
||||
messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Ethernet config invalid: model %s %s",eth_config.model,network_driver?"was not compiled in":"was not found");
|
||||
eth_config.valid = false;
|
||||
}
|
||||
|
||||
if(network_driver){
|
||||
eth_pin.rmii = network_driver->rmii;
|
||||
eth_pin.spi = network_driver->spi;
|
||||
eth_config.rmii = network_driver->rmii;
|
||||
eth_config.spi = network_driver->spi;
|
||||
|
||||
if(network_driver->rmii){
|
||||
if(!GPIO_IS_VALID_GPIO(eth_pin.mdio) || !GPIO_IS_VALID_GPIO(eth_pin.mdc)){
|
||||
messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Ethernet config invalid: %s %s",!GPIO_IS_VALID_GPIO(eth_pin.mdc)?"Invalid MDC":"",!GPIO_IS_VALID_GPIO(eth_pin.mdio)?"Invalid mdio":"");
|
||||
eth_pin.valid = false;
|
||||
if(!GPIO_IS_VALID_GPIO(eth_config.mdio) || !GPIO_IS_VALID_GPIO(eth_config.mdc)){
|
||||
messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Ethernet config invalid: %s %s",!GPIO_IS_VALID_GPIO(eth_config.mdc)?"Invalid MDC":"",!GPIO_IS_VALID_GPIO(eth_config.mdio)?"Invalid mdio":"");
|
||||
eth_config.valid = false;
|
||||
}
|
||||
}
|
||||
else if(network_driver->spi){
|
||||
if(!GPIO_IS_VALID_GPIO(eth_pin.cs)){
|
||||
if(!GPIO_IS_VALID_GPIO(eth_config.cs)){
|
||||
messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Ethernet config invalid: invalid CS pin");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ð_pin;
|
||||
return ð_config;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -468,16 +471,18 @@ const display_config_t * config_display_get(){
|
||||
sscanf(p, "%*[^:]:%u", &dstruct.depth);
|
||||
dstruct.drivername = display_conf_get_driver_name(strchr(p, '=') + 1);
|
||||
}
|
||||
|
||||
if ((p = strcasestr(config, "width")) != NULL) dstruct.width = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "height")) != NULL) dstruct.height = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "reset")) != NULL) dstruct.RST_pin = atoi(strchr(p, '=') + 1);
|
||||
|
||||
PARSE_PARAM(config, "width", '=', dstruct.width);
|
||||
PARSE_PARAM(config, "height", '=', dstruct.height);
|
||||
PARSE_PARAM(config, "reset", '=', dstruct.RST_pin);
|
||||
PARSE_PARAM(config, "address", '=', dstruct.address);
|
||||
PARSE_PARAM(config, "cs", '=', dstruct.CS_pin);
|
||||
PARSE_PARAM(config, "speed", '=', dstruct.speed);
|
||||
PARSE_PARAM(config, "back", '=', dstruct.back);
|
||||
|
||||
if (strstr(config, "I2C") ) dstruct.type=i2c_name_type;
|
||||
if ((p = strcasestr(config, "address")) != NULL) dstruct.address = atoi(strchr(p, '=') + 1);
|
||||
if (strstr(config, "SPI") ) dstruct.type=spi_name_type;
|
||||
if ((p = strcasestr(config, "cs")) != NULL) dstruct.CS_pin = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "speed")) != NULL) dstruct.speed = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "back")) != NULL) dstruct.back = atoi(strchr(p, '=') + 1);
|
||||
|
||||
dstruct.hflip= strcasestr(config, "HFlip") ? true : false;
|
||||
dstruct.vflip= strcasestr(config, "VFlip") ? true : false;
|
||||
dstruct.rotate= strcasestr(config, "rotate") ? true : false;
|
||||
@@ -488,7 +493,7 @@ const display_config_t * config_display_get(){
|
||||
*
|
||||
*/
|
||||
const i2c_config_t * config_i2c_get(int * i2c_port) {
|
||||
char *nvs_item, *p;
|
||||
char *nvs_item;
|
||||
static i2c_config_t i2c = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = -1,
|
||||
@@ -502,10 +507,10 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
|
||||
|
||||
nvs_item = config_alloc_get(NVS_TYPE_STR, "i2c_config");
|
||||
if (nvs_item) {
|
||||
if ((p = strcasestr(nvs_item, "scl")) != NULL) i2c.scl_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "sda")) != NULL) i2c.sda_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "speed")) != NULL) i2c.master.clk_speed = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "port")) != NULL) i2c_system_port = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(nvs_item, "scl", '=', i2c.scl_io_num);
|
||||
PARSE_PARAM(nvs_item, "sda", '=', i2c.sda_io_num);
|
||||
PARSE_PARAM(nvs_item, "speed", '=', i2c.master.clk_speed);
|
||||
PARSE_PARAM(nvs_item, "port", '=', i2c_system_port);
|
||||
free(nvs_item);
|
||||
}
|
||||
if(i2c_port) {
|
||||
@@ -518,6 +523,46 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
|
||||
return &i2c;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Get IO expander config structure from config string
|
||||
*/
|
||||
const gpio_exp_config_t* config_gpio_exp_get(int index) {
|
||||
char *nvs_item, *item, *p;
|
||||
static gpio_exp_config_t config;
|
||||
|
||||
// re-initialize config every time
|
||||
memset(&config, 0, sizeof(config));
|
||||
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");
|
||||
if (!nvs_item || !*nvs_item) return NULL;
|
||||
|
||||
// search index items
|
||||
for (item = strtok(nvs_item, ";"); index && item; index--) {
|
||||
if ((item = strtok(NULL, ";")) == NULL) {
|
||||
free(nvs_item);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PARSE_PARAM(item, "addr", '=', config.phy.addr);
|
||||
PARSE_PARAM(item, "cs", '=', config.phy.cs_pin);
|
||||
PARSE_PARAM(item, "speed", '=', config.phy.speed);
|
||||
PARSE_PARAM(item, "intr", '=', config.intr);
|
||||
PARSE_PARAM(item, "base", '=', config.base);
|
||||
PARSE_PARAM(item, "count", '=', config.count);
|
||||
PARSE_PARAM_STR(item, "model", '=', config.model, 31);
|
||||
|
||||
if ((p = strcasestr(item, "port")) != NULL) {
|
||||
char port[8] = "";
|
||||
sscanf(p, "%*[^=]=%7[^,]", port);
|
||||
if (strcasestr(port, "dac")) config.phy.port = 0;
|
||||
}
|
||||
|
||||
free(nvs_item);
|
||||
return &config;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -596,18 +641,19 @@ const set_GPIO_struct_t * get_gpio_struct(){
|
||||
*
|
||||
*/
|
||||
const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host) {
|
||||
char *nvs_item, *p;
|
||||
char *nvs_item;
|
||||
static EXT_RAM_ATTR spi_bus_config_t spi;
|
||||
memset(&spi, 0xFF, sizeof(spi));
|
||||
memset(&spi, 0xff, sizeof(spi));
|
||||
|
||||
nvs_item = config_alloc_get_str("spi_config", CONFIG_SPI_CONFIG, NULL);
|
||||
if (nvs_item) {
|
||||
if ((p = strcasestr(nvs_item, "data")) != NULL) spi.mosi_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "mosi")) != NULL) spi.mosi_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "miso")) != NULL) spi.miso_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "clk")) != NULL) spi.sclk_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "dc")) != NULL) spi_system_dc_gpio = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "host")) != NULL) spi_system_host = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(nvs_item, "data", '=', spi.mosi_io_num);
|
||||
PARSE_PARAM(nvs_item, "mosi", '=', spi.mosi_io_num);
|
||||
PARSE_PARAM(nvs_item, "miso", '=', spi.miso_io_num);
|
||||
PARSE_PARAM(nvs_item, "clk", '=', spi.sclk_io_num);
|
||||
PARSE_PARAM(nvs_item, "dc", '=', spi_system_dc_gpio);
|
||||
// only VSPI (1) can be used as Flash and PSRAM run at 80MHz
|
||||
// if ((p = strcasestr(nvs_item, "host")) != NULL) spi_system_host = atoi(strchr(p, '=') + 1);
|
||||
free(nvs_item);
|
||||
}
|
||||
if(spi_host) *spi_host = spi_system_host;
|
||||
@@ -642,11 +688,11 @@ const rotary_struct_t * config_rotary_get() {
|
||||
char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
|
||||
if (config && *config) {
|
||||
char *p;
|
||||
|
||||
|
||||
// parse config
|
||||
if ((p = strcasestr(config, "A")) != NULL) rotary.A = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "B")) != NULL) rotary.B = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "SW")) != NULL) rotary.SW = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(config, "A", '=', rotary.A);
|
||||
PARSE_PARAM(config, "B", '=', rotary.B);
|
||||
PARSE_PARAM(config, "SW", '=', rotary.SW);
|
||||
if ((p = strcasestr(config, "knobonly")) != NULL) {
|
||||
p = strchr(p, '=');
|
||||
rotary.knobonly = true;
|
||||
@@ -691,13 +737,10 @@ cJSON * add_gpio_for_value(cJSON * list,const char * name,int gpio, const char *
|
||||
*/
|
||||
cJSON * add_gpio_for_name(cJSON * list,const char * nvs_entry,const char * name, const char * prefix, bool fixed){
|
||||
cJSON * llist = list?list:cJSON_CreateArray();
|
||||
char *p;
|
||||
int gpioNum=0;
|
||||
if ((p = strcasestr(nvs_entry, name)) != NULL) {
|
||||
gpioNum = atoi(strchr(p, '=') + 1);
|
||||
if(gpioNum>=0){
|
||||
cJSON_AddItemToArray(llist,get_gpio_entry(name,prefix,gpioNum,fixed));
|
||||
}
|
||||
PARSE_PARAM(nvs_entry, name, '=', gpioNum);
|
||||
if(gpioNum>=0){
|
||||
cJSON_AddItemToArray(llist,get_gpio_entry(name,prefix,gpioNum,fixed));
|
||||
}
|
||||
return llist;
|
||||
}
|
||||
@@ -1059,14 +1102,11 @@ cJSON * get_gpio_list(bool refresh) {
|
||||
#ifndef CONFIG_BAT_LOCKED
|
||||
char *bat_config = config_alloc_get_default(NVS_TYPE_STR, "bat_config", NULL, 0);
|
||||
if (bat_config) {
|
||||
char *p;
|
||||
int channel;
|
||||
if ((p = strcasestr(bat_config, "channel") ) != NULL) {
|
||||
channel = atoi(strchr(p, '=') + 1);
|
||||
if(channel != -1){
|
||||
if(adc1_pad_get_io_num(channel,&gpio_num )==ESP_OK){
|
||||
cJSON_AddItemToArray(gpio_list,get_gpio_entry("bat","other",gpio_num,false));
|
||||
}
|
||||
int channel = -1;
|
||||
PARSE_PARAM(bat_config, "channel", '=', channel);
|
||||
if(channel != -1){
|
||||
if(adc1_pad_get_io_num(channel,&gpio_num )==ESP_OK){
|
||||
cJSON_AddItemToArray(gpio_list,get_gpio_entry("bat","other",gpio_num,false));
|
||||
}
|
||||
}
|
||||
free(bat_config);
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "gpio_exp.h"
|
||||
|
||||
extern const char *i2c_name_type;
|
||||
extern const char *spi_name_type;
|
||||
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
@@ -97,6 +98,7 @@ esp_err_t config_i2s_set(const i2s_platform_config_t * config, const char *
|
||||
esp_err_t config_spi_set(const spi_bus_config_t * config, int host, int dc);
|
||||
const i2c_config_t * config_i2c_get(int * i2c_port);
|
||||
const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host);
|
||||
const gpio_exp_config_t * config_gpio_exp_get(int index);
|
||||
void parse_set_GPIO(void (*cb)(int gpio, char *value));
|
||||
const i2s_platform_config_t * config_dac_get();
|
||||
const i2s_platform_config_t * config_spdif_get( );
|
||||
|
||||
@@ -67,12 +67,12 @@ static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(
|
||||
static const char * TAG = "audio controls";
|
||||
static actrls_config_t *json_config;
|
||||
cJSON * control_profiles = NULL;
|
||||
static actrls_t default_controls, current_controls;
|
||||
static EXT_RAM_ATTR actrls_t default_controls, current_controls;
|
||||
static actrls_hook_t *default_hook, *current_hook;
|
||||
static bool default_raw_controls, current_raw_controls;
|
||||
static actrls_ir_handler_t *default_ir_handler, *current_ir_handler;
|
||||
|
||||
static struct {
|
||||
static EXT_RAM_ATTR struct {
|
||||
bool long_state;
|
||||
bool volume_lock;
|
||||
TimerHandle_t timer;
|
||||
@@ -137,10 +137,10 @@ esp_err_t actrls_init(const char *profile_name) {
|
||||
int A = -1, B = -1, SW = -1, longpress = 0;
|
||||
|
||||
// parse config
|
||||
if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "knobonly")) != NULL) {
|
||||
PARSE_PARAM(config, "A", '=', A);
|
||||
PARSE_PARAM(config, "B", '=', B);
|
||||
PARSE_PARAM(config, "SW", '=', SW);
|
||||
if ((p = strcasestr(config, "knobonly"))) {
|
||||
p = strchr(p, '=');
|
||||
int double_press = p ? atoi(p + 1) : 350;
|
||||
rotary.timer = xTimerCreate("knobTimer", double_press / portTICK_RATE_MS, pdFALSE, NULL, rotary_timer);
|
||||
|
||||
@@ -79,13 +79,12 @@ void battery_svc_init(void) {
|
||||
|
||||
char *nvs_item = config_alloc_get_default(NVS_TYPE_STR, "bat_config", "n", 0);
|
||||
if (nvs_item) {
|
||||
char *p;
|
||||
#ifndef CONFIG_BAT_LOCKED
|
||||
if ((p = strcasestr(nvs_item, "channel")) != NULL) battery.channel = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "scale")) != NULL) battery.scale = atof(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "atten")) != NULL) battery.attenuation = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(nvs_item, "channel", '=', battery.channel);
|
||||
PARSE_PARAM(nvs_item, "scale", '=', battery.scale);
|
||||
PARSE_PARAM(nvs_item, "atten", '=', battery.attenuation);
|
||||
#endif
|
||||
if ((p = strcasestr(nvs_item, "cells")) != NULL) battery.cells = atof(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(nvs_item, "cells", '=', battery.cells);
|
||||
free(nvs_item);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,16 +21,17 @@
|
||||
#include "esp_task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "buttons.h"
|
||||
#include "rotary_encoder.h"
|
||||
#include "globdefs.h"
|
||||
|
||||
static const char * TAG = "buttons";
|
||||
|
||||
static int n_buttons = 0;
|
||||
static EXT_RAM_ATTR int n_buttons;
|
||||
|
||||
#define BUTTON_STACK_SIZE 4096
|
||||
#define MAX_BUTTONS 16
|
||||
#define MAX_BUTTONS 32
|
||||
#define DEBOUNCE 50
|
||||
#define BUTTON_QUEUE_LEN 10
|
||||
|
||||
@@ -47,6 +48,7 @@ static EXT_RAM_ATTR struct button_s {
|
||||
TimerHandle_t timer;
|
||||
} buttons[MAX_BUTTONS];
|
||||
|
||||
// can't use EXT_RAM_ATTR for initialized structure
|
||||
static struct {
|
||||
int gpio, level;
|
||||
struct button_s *button;
|
||||
@@ -67,10 +69,11 @@ static EXT_RAM_ATTR struct {
|
||||
infrared_handler handler;
|
||||
} infrared;
|
||||
|
||||
static xQueueHandle button_evt_queue;
|
||||
static QueueSetHandle_t common_queue_set;
|
||||
static EXT_RAM_ATTR QueueHandle_t button_queue;
|
||||
static EXT_RAM_ATTR QueueSetHandle_t common_queue_set;
|
||||
|
||||
static void buttons_task(void* arg);
|
||||
static void buttons_handler(struct button_s *button, int level);
|
||||
|
||||
/****************************************************************************************
|
||||
* Start task needed by button,s rotaty and infrared
|
||||
@@ -86,40 +89,33 @@ static void common_task_init(void) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* GPIO low-level handler
|
||||
* GPIO low-level ISR handler
|
||||
*/
|
||||
static void IRAM_ATTR gpio_isr_handler(void* arg)
|
||||
{
|
||||
struct button_s *button = (struct button_s*) arg;
|
||||
BaseType_t woken = pdFALSE;
|
||||
|
||||
if (xTimerGetPeriod(button->timer) > button->debounce / portTICK_RATE_MS) xTimerChangePeriodFromISR(button->timer, button->debounce / portTICK_RATE_MS, &woken); // does that restart the timer?
|
||||
else xTimerResetFromISR(button->timer, &woken);
|
||||
if (xTimerGetPeriod(button->timer) > pdMS_TO_TICKS(button->debounce)) {
|
||||
if (button->gpio < GPIO_NUM_MAX) xTimerChangePeriodFromISR(button->timer, pdMS_TO_TICKS(button->debounce), &woken);
|
||||
else xTimerChangePeriod(button->timer, pdMS_TO_TICKS(button->debounce), pdMS_TO_TICKS(10));
|
||||
} else {
|
||||
if (button->gpio < GPIO_NUM_MAX) xTimerResetFromISR(button->timer, &woken);
|
||||
else xTimerReset(button->timer, portMAX_DELAY);
|
||||
}
|
||||
|
||||
if (woken) portYIELD_FROM_ISR();
|
||||
|
||||
ESP_EARLY_LOGD(TAG, "INT gpio %u level %u", button->gpio, button->level);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Buttons debounce/longpress timer
|
||||
*/
|
||||
static void buttons_timer( TimerHandle_t xTimer ) {
|
||||
static void buttons_timer_handler( TimerHandle_t xTimer ) {
|
||||
struct button_s *button = (struct button_s*) pvTimerGetTimerID (xTimer);
|
||||
|
||||
button->level = gpio_get_level(button->gpio);
|
||||
if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
|
||||
|
||||
if (button->long_press && !button->long_timer && button->level == button->type) {
|
||||
// detect a long press, so hold event generation
|
||||
ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
|
||||
xTimerChangePeriod(xTimer, button->long_press / portTICK_RATE_MS, 0);
|
||||
button->long_timer = true;
|
||||
} else {
|
||||
// send a button pressed/released event (content is copied in queue)
|
||||
ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
|
||||
// queue will have a copy of button's context
|
||||
xQueueSend(button_evt_queue, button, 0);
|
||||
button->long_timer = false;
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -133,11 +129,33 @@ static void buttons_polling( TimerHandle_t xTimer ) {
|
||||
|
||||
if (level != polled_gpio[i].level) {
|
||||
polled_gpio[i].level = level;
|
||||
buttons_timer(polled_gpio[i].button->timer);
|
||||
buttons_handler(polled_gpio[i].button, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Buttons timer handler for press/longpress
|
||||
*/
|
||||
static void buttons_handler(struct button_s *button, int level) {
|
||||
button->level = level;
|
||||
|
||||
if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
|
||||
|
||||
if (button->long_press && !button->long_timer && button->level == button->type) {
|
||||
// detect a long press, so hold event generation
|
||||
ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
|
||||
xTimerChangePeriod(button->timer, button->long_press / portTICK_RATE_MS, 0);
|
||||
button->long_timer = true;
|
||||
} else {
|
||||
// send a button pressed/released event (content is copied in queue)
|
||||
ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
|
||||
// queue will have a copy of button's context
|
||||
xQueueSend(button_queue, button, 0);
|
||||
button->long_timer = false;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Tasks that calls the appropriate functions when buttons are pressed
|
||||
*/
|
||||
@@ -150,13 +168,13 @@ static void buttons_task(void* arg) {
|
||||
// wait on button, rotary and infrared queues
|
||||
if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue;
|
||||
|
||||
if (xActivatedMember == button_evt_queue) {
|
||||
if (xActivatedMember == button_queue) {
|
||||
struct button_s button;
|
||||
button_event_e event;
|
||||
button_press_e press;
|
||||
|
||||
// received a button event
|
||||
xQueueReceive(button_evt_queue, &button, 0);
|
||||
xQueueReceive(button_queue, &button, 0);
|
||||
|
||||
event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;
|
||||
|
||||
@@ -175,18 +193,18 @@ static void buttons_task(void* arg) {
|
||||
if (event == BUTTON_RELEASED) {
|
||||
// early release of a long-press button, send press/release
|
||||
if (!button.shifting) {
|
||||
(*button.handler)(button.client, BUTTON_PRESSED, press, false);
|
||||
(*button.handler)(button.client, BUTTON_RELEASED, press, false);
|
||||
button.handler(button.client, BUTTON_PRESSED, press, false);
|
||||
button.handler(button.client, BUTTON_RELEASED, press, false);
|
||||
}
|
||||
// button is a copy, so need to go to real context
|
||||
button.self->shifting = false;
|
||||
} else if (!button.shifting) {
|
||||
// normal long press and not shifting so don't discard
|
||||
(*button.handler)(button.client, BUTTON_PRESSED, press, true);
|
||||
button.handler(button.client, BUTTON_PRESSED, press, true);
|
||||
}
|
||||
} else {
|
||||
// normal press/release of a button or release of a long-press button
|
||||
if (!button.shifting) (*button.handler)(button.client, event, press, button.long_press);
|
||||
if (!button.shifting) button.handler(button.client, event, press, button.long_press);
|
||||
// button is a copy, so need to go to real context
|
||||
button.self->shifting = false;
|
||||
}
|
||||
@@ -195,12 +213,12 @@ static void buttons_task(void* arg) {
|
||||
|
||||
// received a rotary event
|
||||
xQueueReceive(rotary.queue, &event, 0);
|
||||
|
||||
|
||||
ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position,
|
||||
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
|
||||
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
|
||||
|
||||
rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
|
||||
ROTARY_RIGHT : ROTARY_LEFT, false);
|
||||
ROTARY_RIGHT : ROTARY_LEFT, false);
|
||||
} else {
|
||||
// this is IR
|
||||
infrared_receive(infrared.rb, infrared.handler);
|
||||
@@ -224,9 +242,9 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
|
||||
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);
|
||||
|
||||
if (!n_buttons) {
|
||||
button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
|
||||
button_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
|
||||
common_task_init();
|
||||
xQueueAddToSet( button_evt_queue, common_queue_set );
|
||||
xQueueAddToSet( button_queue, common_queue_set );
|
||||
}
|
||||
|
||||
// just in case this structure is allocated in a future release
|
||||
@@ -240,7 +258,7 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
|
||||
buttons[n_buttons].long_press = long_press;
|
||||
buttons[n_buttons].shifter_gpio = shifter_gpio;
|
||||
buttons[n_buttons].type = type;
|
||||
buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer);
|
||||
buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer_handler);
|
||||
buttons[n_buttons].self = buttons + n_buttons;
|
||||
|
||||
for (int i = 0; i < n_buttons; i++) {
|
||||
@@ -257,24 +275,21 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
|
||||
}
|
||||
}
|
||||
|
||||
gpio_pad_select_gpio(gpio);
|
||||
gpio_set_direction(gpio, GPIO_MODE_INPUT);
|
||||
|
||||
// we need any edge detection
|
||||
gpio_set_intr_type(gpio, GPIO_INTR_ANYEDGE);
|
||||
gpio_pad_select_gpio_x(gpio);
|
||||
gpio_set_direction_x(gpio, GPIO_MODE_INPUT);
|
||||
|
||||
// do we need pullup or pulldown
|
||||
if (pull) {
|
||||
if (GPIO_IS_VALID_OUTPUT_GPIO(gpio)) {
|
||||
if (type == BUTTON_LOW) gpio_set_pull_mode(gpio, GPIO_PULLUP_ONLY);
|
||||
else gpio_set_pull_mode(gpio, GPIO_PULLDOWN_ONLY);
|
||||
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);
|
||||
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) {
|
||||
@@ -282,19 +297,21 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
|
||||
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 timers and ISR is this is not a polled gpio
|
||||
// only create ISR if this is not a polled gpio
|
||||
if (gpio != -1) {
|
||||
gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
|
||||
gpio_intr_enable(gpio);
|
||||
// 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++;
|
||||
@@ -362,7 +379,7 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Create rotary encoder
|
||||
* Rotary encoder handler
|
||||
*/
|
||||
static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
|
||||
ESP_LOGI(TAG, "Rotary push-button %d", event);
|
||||
|
||||
@@ -21,13 +21,3 @@ typedef struct {
|
||||
int timer, base_channel, max;
|
||||
} pwm_system_t;
|
||||
extern pwm_system_t pwm_system;
|
||||
#ifdef CONFIG_SQUEEZEAMP
|
||||
#define ADAC dac_tas57xx
|
||||
#elif defined(CONFIG_A1S)
|
||||
#define ADAC dac_a1s
|
||||
#else
|
||||
#define ADAC dac_external
|
||||
#endif
|
||||
void * malloc_init_external(size_t sz);
|
||||
void * clone_obj_psram(void * source, size_t source_sz);
|
||||
char * strdup_psram(const char * source);
|
||||
738
components/services/gpio_exp.c
Normal file
738
components/services/gpio_exp.c
Normal file
@@ -0,0 +1,738 @@
|
||||
/* GDS Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_task.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "gpio_exp.h"
|
||||
|
||||
#define GPIO_EXP_INTR 0x100
|
||||
#define GPIO_EXP_WRITE 0x200
|
||||
|
||||
/*
|
||||
shadow register is both output and input, so we assume that reading to the
|
||||
ports also reads the value set on output
|
||||
*/
|
||||
|
||||
typedef struct gpio_exp_s {
|
||||
uint32_t first, last;
|
||||
int intr;
|
||||
bool intr_pending;
|
||||
struct {
|
||||
struct gpio_exp_phy_s phy;
|
||||
spi_device_handle_t spi_handle;
|
||||
};
|
||||
uint32_t shadow, pending;
|
||||
TickType_t age;
|
||||
SemaphoreHandle_t mutex;
|
||||
uint32_t r_mask, w_mask;
|
||||
uint32_t pullup, pulldown;
|
||||
struct gpio_exp_isr_s {
|
||||
gpio_isr_t handler;
|
||||
void *arg;
|
||||
TimerHandle_t timer;
|
||||
} isr[32];
|
||||
struct gpio_exp_model_s const *model;
|
||||
} gpio_exp_t;
|
||||
|
||||
typedef struct {
|
||||
enum { ASYNC_WRITE } type;
|
||||
int gpio;
|
||||
int level;
|
||||
gpio_exp_t *expander;
|
||||
} queue_request_t;
|
||||
|
||||
static const char TAG[] = "gpio expander";
|
||||
|
||||
static void IRAM_ATTR intr_isr_handler(void* arg);
|
||||
static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio);
|
||||
|
||||
static void pca9535_set_direction(gpio_exp_t* self);
|
||||
static uint32_t pca9535_read(gpio_exp_t* self);
|
||||
static void pca9535_write(gpio_exp_t* self);
|
||||
|
||||
static uint32_t pca85xx_read(gpio_exp_t* self);
|
||||
static void pca85xx_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 uint32_t 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 uint32_t mcp23s17_read(gpio_exp_t* self);
|
||||
static void mcp23s17_write(gpio_exp_t* self);
|
||||
|
||||
static void service_handler(void *arg);
|
||||
static void debounce_handler( TimerHandle_t xTimer );
|
||||
|
||||
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 {
|
||||
char *model;
|
||||
gpio_int_type_t trigger;
|
||||
esp_err_t (*init)(gpio_exp_t* self);
|
||||
uint32_t (*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[] = {
|
||||
{ .model = "pca9535",
|
||||
.trigger = GPIO_INTR_NEGEDGE,
|
||||
.set_direction = pca9535_set_direction,
|
||||
.read = pca9535_read,
|
||||
.write = pca9535_write, },
|
||||
{ .model = "pca85xx",
|
||||
.trigger = GPIO_INTR_NEGEDGE,
|
||||
.read = pca85xx_read,
|
||||
.write = pca85xx_write, },
|
||||
{ .model = "mcp23017",
|
||||
.trigger = GPIO_INTR_NEGEDGE,
|
||||
.init = mcp23017_init,
|
||||
.set_direction = mcp23017_set_direction,
|
||||
.set_pull_mode = mcp23017_set_pull_mode,
|
||||
.read = mcp23017_read,
|
||||
.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 QueueHandle_t message_queue;
|
||||
static EXT_RAM_ATTR gpio_exp_t expanders[4];
|
||||
static EXT_RAM_ATTR TaskHandle_t service_task;
|
||||
|
||||
/******************************************************************************
|
||||
* Retrieve base from an expander reference
|
||||
*/
|
||||
uint32_t gpio_exp_get_base(gpio_exp_t *expander) {
|
||||
return expander->first;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Retrieve reference from a GPIO
|
||||
*/
|
||||
gpio_exp_t *gpio_exp_get_expander(int gpio) {
|
||||
int _gpio = gpio;
|
||||
return find_expander(NULL, &_gpio);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Create an I2C expander
|
||||
*/
|
||||
gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
|
||||
gpio_exp_t *expander = expanders + n_expanders;
|
||||
|
||||
if (config->base < GPIO_NUM_MAX || n_expanders == sizeof(expanders)/sizeof(gpio_exp_t)) {
|
||||
ESP_LOGE(TAG, "Base %d GPIO must be at least %d for %s or too many expanders %d", config->base, GPIO_NUM_MAX, config->model, n_expanders);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// See if we know that model (expanders is zero-initialized)
|
||||
for (int i = 0; !expander->model && i < sizeof(registered)/sizeof(struct gpio_exp_model_s); i++) {
|
||||
if (strcasestr(config->model, registered[i].model)) expander->model = registered + i;
|
||||
}
|
||||
|
||||
// well... try again
|
||||
if (!expander->model) {
|
||||
ESP_LOGE(TAG, "Unknown GPIO expansion chip %s", config->model);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(&expander->phy, &config->phy, sizeof(struct gpio_exp_phy_s));
|
||||
|
||||
// try to initialize the expander if required
|
||||
if (expander->model->init && expander->model->init(expander) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Cannot create GPIO expander %s, check i2c/spi configuration", config->model);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
n_expanders++;
|
||||
expander->first = config->base;
|
||||
expander->last = config->base + config->count - 1;
|
||||
expander->intr = config->intr;
|
||||
expander->mutex = xSemaphoreCreateMutex();
|
||||
|
||||
// create a task to handle asynchronous requests (only write at this time)
|
||||
if (!message_queue) {
|
||||
// we allocate TCB but stack is static to avoid SPIRAM fragmentation
|
||||
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
static EXT_RAM_ATTR StackType_t xStack[4*1024] __attribute__ ((aligned (4)));
|
||||
|
||||
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
|
||||
if (config->intr >= 0) {
|
||||
gpio_pad_select_gpio(config->intr);
|
||||
gpio_set_direction(config->intr, GPIO_MODE_INPUT);
|
||||
|
||||
switch (expander->model->trigger) {
|
||||
case GPIO_INTR_NEGEDGE:
|
||||
case GPIO_INTR_LOW_LEVEL:
|
||||
gpio_set_pull_mode(config->intr, GPIO_PULLUP_ONLY);
|
||||
break;
|
||||
case GPIO_INTR_POSEDGE:
|
||||
case GPIO_INTR_HIGH_LEVEL:
|
||||
gpio_set_pull_mode(config->intr, GPIO_PULLDOWN_ONLY);
|
||||
break;
|
||||
default:
|
||||
gpio_set_pull_mode(config->intr, GPIO_PULLUP_PULLDOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
gpio_set_intr_type(config->intr, expander->model->trigger);
|
||||
gpio_isr_handler_add(config->intr, intr_isr_handler, expander);
|
||||
gpio_intr_enable(config->intr);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create GPIO expander %s at base %u with INT %d at @%x on port/host %d/%d", config->model, config->base, config->intr, config->phy.addr, config->phy.port, config->phy.host);
|
||||
return expander;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Add ISR handler for a GPIO
|
||||
*/
|
||||
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;
|
||||
|
||||
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 );
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Remove ISR handler for a GPIO
|
||||
*/
|
||||
esp_err_t gpio_exp_isr_handler_remove(int gpio, struct gpio_exp_s *expander) {
|
||||
if (gpio < GPIO_NUM_MAX && !expander) return gpio_isr_handler_remove(gpio);
|
||||
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
if (expander->isr[gpio].timer) xTimerDelete(expander->isr[gpio].timer, portMAX_DELAY);
|
||||
memset(expander->isr + gpio, 0, sizeof(struct gpio_exp_isr_s));
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Set GPIO direction
|
||||
*/
|
||||
esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, gpio_exp_t *expander) {
|
||||
if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_direction(gpio, mode);
|
||||
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
|
||||
|
||||
if (mode == GPIO_MODE_INPUT) {
|
||||
expander->r_mask |= 1 << gpio;
|
||||
expander->shadow = expander->model->read(expander);
|
||||
expander->age = ~xTaskGetTickCount();
|
||||
} else {
|
||||
expander->w_mask |= 1 << gpio;
|
||||
}
|
||||
|
||||
if (expander->r_mask & expander->w_mask) {
|
||||
xSemaphoreGive(expander->mutex);
|
||||
ESP_LOGE(TAG, "GPIO %d on expander base %u can't be r/w", gpio, expander->first);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// most expanders want unconfigured GPIO to be set to output
|
||||
if (expander->model->set_direction) expander->model->set_direction(expander);
|
||||
|
||||
xSemaphoreGive(expander->mutex);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Get GPIO level with cache
|
||||
*/
|
||||
int gpio_exp_get_level(int gpio, int age, gpio_exp_t *expander) {
|
||||
if (gpio < GPIO_NUM_MAX && !expander) return gpio_get_level(gpio);
|
||||
if ((expander = find_expander(expander, &gpio)) == NULL) return -1;
|
||||
uint32_t now = xTaskGetTickCount();
|
||||
|
||||
// return last thing we had if we can't get the mutex
|
||||
if (xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)) == pdFALSE) {
|
||||
ESP_LOGW(TAG, "Can't get mutex for GPIO %d", expander->first + gpio);
|
||||
return (expander->shadow >> gpio) & 0x01;
|
||||
}
|
||||
|
||||
// re-read the expander if data is too old
|
||||
if (age >= 0 && now - expander->age >= pdMS_TO_TICKS(age)) {
|
||||
uint32_t value = expander->model->read(expander);
|
||||
expander->pending |= (expander->shadow ^ value) & expander->r_mask;
|
||||
expander->shadow = value;
|
||||
expander->age = now;
|
||||
}
|
||||
|
||||
// clear pending bit
|
||||
expander->pending &= ~(1 << gpio);
|
||||
|
||||
xSemaphoreGive(expander->mutex);
|
||||
|
||||
ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow);
|
||||
return (expander->shadow >> gpio) & 0x01;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Set GPIO level with cache
|
||||
*/
|
||||
esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, gpio_exp_t *expander) {
|
||||
if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_level(gpio, level);
|
||||
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
|
||||
uint32_t mask = 1 << gpio;
|
||||
|
||||
// very limited risk with lack of semaphore here
|
||||
if ((expander->w_mask & mask) == 0) {
|
||||
ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (direct) {
|
||||
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
|
||||
|
||||
level = level ? mask : 0;
|
||||
mask &= expander->shadow;
|
||||
|
||||
// only write if shadow not up to date
|
||||
if ((mask ^ level) && expander->model->write) {
|
||||
expander->shadow = (expander->shadow & ~(mask | level)) | level;
|
||||
expander->model->write(expander);
|
||||
}
|
||||
|
||||
xSemaphoreGive(expander->mutex);
|
||||
ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Set GPIO pullmode
|
||||
*/
|
||||
esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, gpio_exp_t *expander) {
|
||||
if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_pull_mode(gpio, mode);
|
||||
if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) {
|
||||
|
||||
expander->pullup &= ~(1 << gpio);
|
||||
expander->pulldown &= ~(1 << gpio);
|
||||
|
||||
if (mode == GPIO_PULLUP_ONLY || mode == GPIO_PULLUP_PULLDOWN) expander->pullup |= 1 << gpio;
|
||||
if (mode == GPIO_PULLDOWN_ONLY || mode == GPIO_PULLUP_PULLDOWN) expander->pulldown |= 1 << gpio;
|
||||
|
||||
expander->model->set_pull_mode(expander);
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Wrapper function
|
||||
*/
|
||||
esp_err_t gpio_set_pull_mode_x(int gpio, gpio_pull_mode_t mode) {
|
||||
if (gpio < GPIO_NUM_MAX) return gpio_set_pull_mode(gpio, mode);
|
||||
return gpio_exp_set_pull_mode(gpio, mode, NULL);
|
||||
}
|
||||
|
||||
esp_err_t gpio_set_direction_x(int gpio, gpio_mode_t mode) {
|
||||
if (gpio < GPIO_NUM_MAX) return gpio_set_direction(gpio, mode);
|
||||
return gpio_exp_set_direction(gpio, mode, NULL);
|
||||
}
|
||||
|
||||
int gpio_get_level_x(int gpio) {
|
||||
if (gpio < GPIO_NUM_MAX) return gpio_get_level(gpio);
|
||||
return gpio_exp_get_level(gpio, 10, NULL);
|
||||
}
|
||||
|
||||
esp_err_t gpio_set_level_x(int gpio, int level) {
|
||||
if (gpio < GPIO_NUM_MAX) return gpio_set_level(gpio, level);
|
||||
return gpio_exp_set_level(gpio, level, false, NULL);
|
||||
}
|
||||
|
||||
esp_err_t gpio_isr_handler_add_x(int gpio, gpio_isr_t isr_handler, void* args) {
|
||||
if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_add(gpio, isr_handler, args);
|
||||
return gpio_exp_isr_handler_add(gpio, isr_handler, 0, args, NULL);
|
||||
}
|
||||
|
||||
esp_err_t gpio_isr_handler_remove_x(int gpio) {
|
||||
if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_remove(gpio);
|
||||
return gpio_exp_isr_handler_remove(gpio, NULL);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* INTR low-level handler
|
||||
*/
|
||||
static void IRAM_ATTR intr_isr_handler(void* arg) {
|
||||
gpio_exp_t *self = (gpio_exp_t*) arg;
|
||||
BaseType_t woken = pdFALSE;
|
||||
|
||||
// activate all, including ourselves
|
||||
for (int i = 0; i < n_expanders; i++) if (expanders[i].intr == self->intr) expanders[i].intr_pending = true;
|
||||
|
||||
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(self));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* 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;
|
||||
|
||||
// no interrupt for that gpio
|
||||
if (expander->intr < 0) continue;
|
||||
|
||||
// only check expander with pending interrupts
|
||||
gpio_intr_disable(expander->intr);
|
||||
if (!expander->intr_pending) {
|
||||
gpio_intr_enable(expander->intr);
|
||||
continue;
|
||||
}
|
||||
expander->intr_pending = false;
|
||||
gpio_intr_enable(expander->intr);
|
||||
|
||||
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
|
||||
while (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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
DRIVERS
|
||||
****************************************************************************************/
|
||||
|
||||
/****************************************************************************************
|
||||
* PCA9535 family : direction, read and write
|
||||
*/
|
||||
static void pca9535_set_direction(gpio_exp_t* self) {
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x06, self->r_mask, 2);
|
||||
}
|
||||
|
||||
static uint32_t pca9535_read(gpio_exp_t* self) {
|
||||
return i2c_read(self->phy.port, self->phy.addr, 0x00, 2);
|
||||
}
|
||||
|
||||
static void pca9535_write(gpio_exp_t* self) {
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* PCA85xx family : read and write
|
||||
*/
|
||||
static uint32_t pca85xx_read(gpio_exp_t* self) {
|
||||
// must return the full set of pins, not just inputs
|
||||
uint32_t data = i2c_read(self->phy.port, self->phy.addr, 0xff, 2);
|
||||
return (data & self->r_mask) | (self->shadow & ~self->r_mask);
|
||||
}
|
||||
|
||||
static void pca85xx_write(gpio_exp_t* self) {
|
||||
/*
|
||||
There is no good option with this chip: normally, unused pin should be set to input
|
||||
to avoid any conflict but then they float and create tons of suprious. So option 1 is
|
||||
to le tthem float and option 2 is to set them as output to 0.
|
||||
In addition, setting an output pin to 1 equals is making it an input and if this is
|
||||
use to short a led (e.g.) instead of being the sink, the it generates a spurious
|
||||
*/
|
||||
// option 1
|
||||
// i2c_write(self->phy.port, self->phy.addr, 0xff, (self->shadow & self->w_mask) | ~self->w_mask, 2);
|
||||
// option 2
|
||||
i2c_write(self->phy.port, self->phy.addr, 0xff, (self->shadow & self->w_mask) | self->r_mask, 2);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* 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 uint32_t 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) {
|
||||
if ((self->spi_handle = spi_config(&self->phy)) == NULL) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
/*
|
||||
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 uint32_t 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_master_start(cmd);
|
||||
|
||||
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 our 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);
|
||||
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 write failed");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// when using a register, write it's value then the device address again
|
||||
if (reg != 0xff) {
|
||||
i2c_master_write_byte(cmd, (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, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
} else {
|
||||
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
}
|
||||
|
||||
// works with our 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;
|
||||
|
||||
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 );
|
||||
ESP_LOGI(TAG, "SPI expander initialized on host:%d with cs:%d and speed:%dHz", phy->host, phy->cs_pin, config.clock_speed_hz);
|
||||
|
||||
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[0] = data; transaction.tx_data[1] = 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 = heap_caps_calloc(1, sizeof(spi_transaction_t), MALLOC_CAP_DMA);
|
||||
|
||||
// tx_buffer is NULL, nothing to transmit except cmd/addr
|
||||
transaction->flags = SPI_TRANS_USE_RXDATA;
|
||||
transaction->cmd = (addr << 1) | 0x01;
|
||||
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);
|
||||
uint32_t data = *(uint32_t*) transaction->rx_data;
|
||||
free(transaction);
|
||||
|
||||
return data;
|
||||
}
|
||||
61
components/services/gpio_exp.h
Normal file
61
components/services/gpio_exp.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/* GDS Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
struct gpio_exp_s;
|
||||
|
||||
typedef struct {
|
||||
char model[32];
|
||||
int intr;
|
||||
uint8_t count;
|
||||
uint32_t base;
|
||||
struct gpio_exp_phy_s {
|
||||
uint8_t addr;
|
||||
struct { // for I2C
|
||||
uint8_t port;
|
||||
};
|
||||
struct { // for SPI
|
||||
uint32_t speed;
|
||||
uint8_t host;
|
||||
uint8_t cs_pin;
|
||||
};
|
||||
} phy;
|
||||
} gpio_exp_config_t;
|
||||
|
||||
// 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);
|
||||
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_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander);
|
||||
int gpio_exp_get_level(int gpio, int age, struct gpio_exp_s *expander);
|
||||
esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, struct gpio_exp_s *expander);
|
||||
esp_err_t gpio_exp_isr_handler_add(int gpio, gpio_isr_t isr, uint32_t debounce, void *arg, struct gpio_exp_s *expander);
|
||||
esp_err_t gpio_exp_isr_handler_remove(int gpio, struct gpio_exp_s *expander);
|
||||
|
||||
// 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)
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "platform_config.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "led.h"
|
||||
#include "globdefs.h"
|
||||
#include "accessors.h"
|
||||
@@ -40,7 +41,8 @@ static EXT_RAM_ATTR struct led_s {
|
||||
TimerHandle_t timer;
|
||||
} leds[MAX_LED];
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
// can't use EXT_RAM_ATTR for initialized structure
|
||||
static struct {
|
||||
int gpio;
|
||||
int active;
|
||||
int pwm;
|
||||
@@ -53,7 +55,7 @@ static int led_max = 2;
|
||||
*
|
||||
*/
|
||||
static void set_level(struct led_s *led, bool on) {
|
||||
if (led->pwm < 0) gpio_set_level(led->gpio, on ? led->onstate : !led->onstate);
|
||||
if (led->pwm < 0 || led->gpio >= GPIO_NUM_MAX) gpio_set_level_x(led->gpio, on ? led->onstate : !led->onstate);
|
||||
else {
|
||||
ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max));
|
||||
ledc_update_duty(LEDC_HIGH_SPEED_MODE, led->channel);
|
||||
@@ -179,9 +181,9 @@ bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm) {
|
||||
leds[idx].onstate = onstate;
|
||||
leds[idx].pwm = -1;
|
||||
|
||||
if (pwm < 0) {
|
||||
gpio_pad_select_gpio(gpio);
|
||||
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
|
||||
if (pwm < 0 || gpio >= GPIO_NUM_MAX) {
|
||||
gpio_pad_select_gpio_x(gpio);
|
||||
gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
|
||||
} else {
|
||||
leds[idx].channel = pwm_system.base_channel++;
|
||||
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
|
||||
@@ -232,10 +234,10 @@ void led_svc_init(void) {
|
||||
parse_set_GPIO(set_led_gpio);
|
||||
#endif
|
||||
|
||||
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness"), *p;
|
||||
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness");
|
||||
if (nvs_item) {
|
||||
if ((p = strcasestr(nvs_item, "green")) != NULL) green.pwm = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "red")) != NULL) red.pwm = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(nvs_item, "green", '=', green.pwm);
|
||||
PARSE_PARAM(nvs_item, "red", '=', red.pwm);
|
||||
free(nvs_item);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
#include "nvs_utilities.h"
|
||||
#include "platform_esp32.h"
|
||||
#include "messaging.h"
|
||||
#include "trace.h"
|
||||
#include "globdefs.h"
|
||||
#include "tools.h"
|
||||
/************************************
|
||||
* Globals
|
||||
*/
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "accessors.h"
|
||||
#include "messaging.h"
|
||||
#include "cJSON.h"
|
||||
#include "trace.h"
|
||||
#include "tools.h"
|
||||
|
||||
#define MONITOR_TIMER (10*1000)
|
||||
#define SCRATCH_SIZE 256
|
||||
@@ -147,7 +147,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
|
||||
*
|
||||
*/
|
||||
static void jack_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
|
||||
ESP_LOGD(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
|
||||
ESP_LOGI(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
|
||||
if (jack_handler_svc) (*jack_handler_svc)(event == BUTTON_PRESSED);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "gpio_exp.h"
|
||||
|
||||
#define TAG "rotary_encoder"
|
||||
|
||||
@@ -148,7 +149,7 @@ static uint8_t _process(rotary_encoder_info_t * info)
|
||||
if (info != NULL)
|
||||
{
|
||||
// Get state of input pins.
|
||||
uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a);
|
||||
uint8_t pin_state = (gpio_get_level_x(info->pin_b) << 1) | gpio_get_level_x(info->pin_a);
|
||||
|
||||
// Determine new state from the pins and state table.
|
||||
#ifdef ROTARY_ENCODER_DEBUG
|
||||
@@ -198,12 +199,18 @@ static void _isr_rotenc(void * args)
|
||||
.direction = info->state.direction,
|
||||
},
|
||||
};
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
|
||||
if (task_woken)
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
if (info->pin_a < GPIO_NUM_MAX) {
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
|
||||
if (task_woken)
|
||||
{
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
xQueueOverwrite(info->queue, &queue_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,19 +227,19 @@ esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gp
|
||||
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
|
||||
|
||||
// configure GPIOs
|
||||
gpio_pad_select_gpio(info->pin_a);
|
||||
gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY);
|
||||
gpio_set_direction(info->pin_a, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE);
|
||||
gpio_pad_select_gpio_x(info->pin_a);
|
||||
gpio_set_pull_mode_x(info->pin_a, GPIO_PULLUP_ONLY);
|
||||
gpio_set_direction_x(info->pin_a, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type_x(info->pin_a, GPIO_INTR_ANYEDGE);
|
||||
|
||||
gpio_pad_select_gpio(info->pin_b);
|
||||
gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY);
|
||||
gpio_set_direction(info->pin_b, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE);
|
||||
gpio_pad_select_gpio_x(info->pin_b);
|
||||
gpio_set_pull_mode_x(info->pin_b, GPIO_PULLUP_ONLY);
|
||||
gpio_set_direction_x(info->pin_b, GPIO_MODE_INPUT);
|
||||
gpio_set_intr_type_x(info->pin_b, GPIO_INTR_ANYEDGE);
|
||||
|
||||
// install interrupt handlers
|
||||
gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
|
||||
gpio_isr_handler_add(info->pin_b, _isr_rotenc, info);
|
||||
gpio_isr_handler_add_x(info->pin_a, _isr_rotenc, info);
|
||||
gpio_isr_handler_add_x(info->pin_b, _isr_rotenc, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -280,8 +287,8 @@ esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
|
||||
esp_err_t err = ESP_OK;
|
||||
if (info)
|
||||
{
|
||||
gpio_isr_handler_remove(info->pin_a);
|
||||
gpio_isr_handler_remove(info->pin_b);
|
||||
gpio_isr_handler_remove_x(info->pin_a);
|
||||
gpio_isr_handler_remove_x(info->pin_b);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -12,15 +12,13 @@
|
||||
#include "driver/ledc.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "platform_config.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "battery.h"
|
||||
#include "led.h"
|
||||
#include "monitor.h"
|
||||
#include "globdefs.h"
|
||||
#include "accessors.h"
|
||||
#include "messaging.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
||||
extern void battery_svc_init(void);
|
||||
extern void monitor_svc_init(void);
|
||||
@@ -38,48 +36,14 @@ pwm_system_t pwm_system = {
|
||||
|
||||
static const char *TAG = "services";
|
||||
|
||||
|
||||
void * malloc_init_external(size_t sz){
|
||||
void * ptr=NULL;
|
||||
ptr = heap_caps_malloc(sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if(ptr==NULL){
|
||||
ESP_LOGE(TAG,"malloc_init_external: unable to allocate %d bytes of PSRAM!",sz);
|
||||
}
|
||||
else {
|
||||
memset(ptr,0x00,sz);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
void * clone_obj_psram(void * source, size_t source_sz){
|
||||
void * ptr=NULL;
|
||||
ptr = heap_caps_malloc(source_sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if(ptr==NULL){
|
||||
ESP_LOGE(TAG,"clone_obj_psram: unable to allocate %d bytes of PSRAM!",source_sz);
|
||||
}
|
||||
else {
|
||||
memcpy(ptr,source,source_sz);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
char * strdup_psram(const char * source){
|
||||
void * ptr=NULL;
|
||||
size_t source_sz = strlen(source)+1;
|
||||
ptr = heap_caps_malloc(source_sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if(ptr==NULL){
|
||||
ESP_LOGE(TAG,"strdup_psram: unable to allocate %d bytes of PSRAM! Cannot clone string %s",source_sz,source);
|
||||
}
|
||||
else {
|
||||
memset(ptr,0x00,source_sz);
|
||||
strcpy(ptr,source);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void set_power_gpio(int gpio, char *value) {
|
||||
void set_chip_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_pad_select_gpio(gpio);
|
||||
@@ -89,9 +53,26 @@ void set_power_gpio(int gpio, char *value) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +90,8 @@ void services_init(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// set potential power GPIO
|
||||
parse_set_GPIO(set_power_gpio);
|
||||
// set potential power GPIO on chip first in case expanders are power using these
|
||||
parse_set_GPIO(set_chip_power_gpio);
|
||||
|
||||
// shared I2C bus
|
||||
const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
|
||||
@@ -140,6 +121,13 @@ void services_init(void) {
|
||||
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
|
||||
ledc_timer_config_t pwm_timer = {
|
||||
.duty_resolution = LEDC_TIMER_13_BIT,
|
||||
|
||||
Reference in New Issue
Block a user