mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-07 20:17:04 +03:00
add async write and mutex
This commit is contained in:
@@ -10,6 +10,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "esp_task.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "driver/i2c.h"
|
#include "driver/i2c.h"
|
||||||
@@ -27,11 +29,20 @@ static void pca85xx_set_direction(union gpio_exp_phy_u*, uint32_t, uint32_t);
|
|||||||
static int pca85xx_read(union gpio_exp_phy_u*);
|
static int pca85xx_read(union gpio_exp_phy_u*);
|
||||||
static void pca85xx_write(union gpio_exp_phy_u*, uint32_t, uint32_t);
|
static void pca85xx_write(union gpio_exp_phy_u*, uint32_t, uint32_t);
|
||||||
|
|
||||||
|
static void async_handler(void *arg);
|
||||||
|
|
||||||
static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val);
|
static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val);
|
||||||
static uint8_t i2c_read_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg);
|
static uint8_t i2c_read_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg);
|
||||||
static uint16_t i2c_read_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg);
|
static uint16_t i2c_read_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg);
|
||||||
static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data);
|
static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
enum { ASYNC_WRITE } type;
|
||||||
|
int gpio;
|
||||||
|
int level;
|
||||||
|
struct gpio_exp_s *expander;
|
||||||
|
} async_request_t;
|
||||||
|
|
||||||
static const struct gpio_exp_model_s {
|
static const struct gpio_exp_model_s {
|
||||||
char *model;
|
char *model;
|
||||||
gpio_int_type_t trigger;
|
gpio_int_type_t trigger;
|
||||||
@@ -53,13 +64,15 @@ static const struct gpio_exp_model_s {
|
|||||||
.write = pca85xx_write, }
|
.write = pca85xx_write, }
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint8_t n_expanders;
|
static EXT_RAM_ATTR uint8_t n_expanders;
|
||||||
|
static EXT_RAM_ATTR QueueHandle_t async_queue;
|
||||||
|
|
||||||
static EXT_RAM_ATTR struct gpio_exp_s {
|
static EXT_RAM_ATTR struct gpio_exp_s {
|
||||||
uint32_t first, last;
|
uint32_t first, last;
|
||||||
union gpio_exp_phy_u phy;
|
union gpio_exp_phy_u phy;
|
||||||
uint32_t shadow;
|
uint32_t shadow;
|
||||||
TickType_t age;
|
TickType_t age;
|
||||||
|
SemaphoreHandle_t mutex;
|
||||||
uint32_t r_mask, w_mask;
|
uint32_t r_mask, w_mask;
|
||||||
struct {
|
struct {
|
||||||
gpio_exp_isr handler;
|
gpio_exp_isr handler;
|
||||||
@@ -108,9 +121,20 @@ struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config) {
|
|||||||
n_expanders++;
|
n_expanders++;
|
||||||
expander->first = config->base;
|
expander->first = config->base;
|
||||||
expander->last = config->base + config->count - 1;
|
expander->last = config->base + config->count - 1;
|
||||||
|
expander->mutex = xSemaphoreCreateMutex();
|
||||||
memcpy(&expander->phy, &config->phy, sizeof(union gpio_exp_phy_u));
|
memcpy(&expander->phy, &config->phy, sizeof(union gpio_exp_phy_u));
|
||||||
if (expander->model->init) expander->model->init(&expander->phy);
|
if (expander->model->init) expander->model->init(&expander->phy);
|
||||||
|
|
||||||
|
// create a task to handle asynchronous requests (only write at this time)
|
||||||
|
if (!async_queue) {
|
||||||
|
// we allocate TCB but stack is staic to avoid SPIRAM fragmentation
|
||||||
|
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||||
|
static EXT_RAM_ATTR StackType_t xStack[2*1024] __attribute__ ((aligned (4)));
|
||||||
|
|
||||||
|
xTaskCreateStatic(async_handler, "gpio_expander", sizeof(xStack), NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer);
|
||||||
|
async_queue = xQueueCreate(4, sizeof(async_request_t));
|
||||||
|
}
|
||||||
|
|
||||||
// set interrupt if possible
|
// set interrupt if possible
|
||||||
if (config->intr > 0) {
|
if (config->intr > 0) {
|
||||||
gpio_pad_select_gpio(config->intr);
|
gpio_pad_select_gpio(config->intr);
|
||||||
@@ -143,15 +167,19 @@ struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config) {
|
|||||||
* Add ISR handler
|
* Add ISR handler
|
||||||
*/
|
*/
|
||||||
bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, struct gpio_exp_s *expander) {
|
bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, struct gpio_exp_s *expander) {
|
||||||
|
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(expander->isr)/sizeof(*expander->isr); i++) {
|
for (int i = 0; i < sizeof(expander->isr)/sizeof(*expander->isr); i++) {
|
||||||
if (!expander->isr[i].handler) {
|
if (!expander->isr[i].handler) {
|
||||||
expander->isr[i].handler = isr;
|
expander->isr[i].handler = isr;
|
||||||
expander->isr[i].arg = arg;
|
expander->isr[i].arg = arg;
|
||||||
ESP_LOGI(TAG, "Added new ISR for expander base %d", expander->first);
|
ESP_LOGI(TAG, "Added new ISR for expander base %d", expander->first);
|
||||||
|
xSemaphoreGive(expander->mutex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xSemaphoreGive(expander->mutex);
|
||||||
ESP_LOGE(TAG, "No room left to add new ISR");
|
ESP_LOGE(TAG, "No room left to add new ISR");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -162,6 +190,9 @@ bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, struct gpio_exp_s *expander)
|
|||||||
struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander) {
|
struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander) {
|
||||||
if ((expander = find_expander(expander, &gpio)) == NULL) return NULL;
|
if ((expander = find_expander(expander, &gpio)) == NULL) return NULL;
|
||||||
|
|
||||||
|
int64_t v = esp_timer_get_time();
|
||||||
|
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
|
||||||
|
|
||||||
if (mode == GPIO_MODE_INPUT) {
|
if (mode == GPIO_MODE_INPUT) {
|
||||||
expander->r_mask |= 1 << gpio;
|
expander->r_mask |= 1 << gpio;
|
||||||
expander->age = ~xTaskGetTickCount();
|
expander->age = ~xTaskGetTickCount();
|
||||||
@@ -170,13 +201,16 @@ struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expander->r_mask & expander->w_mask) {
|
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);
|
ESP_LOGE(TAG, "GPIO %d on expander base %u can't be r/w", gpio, expander->first);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// most expanders want unconfigured GPIO to be set to output
|
// most expanders want unconfigured GPIO to be set to output
|
||||||
if (expander->model->set_direction) expander->model->set_direction(&expander->phy, expander->r_mask, expander->w_mask);
|
if (expander->model->set_direction) expander->model->set_direction(&expander->phy, expander->r_mask, expander->w_mask);
|
||||||
|
|
||||||
|
xSemaphoreGive(expander->mutex);
|
||||||
|
ESP_LOGW(TAG, "set took %lld µs", esp_timer_get_time() - v);
|
||||||
return expander;
|
return expander;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,10 +221,16 @@ int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander) {
|
|||||||
if ((expander = find_expander(expander, &gpio)) == NULL) return -1;
|
if ((expander = find_expander(expander, &gpio)) == NULL) return -1;
|
||||||
uint32_t now = xTaskGetTickCount();
|
uint32_t now = xTaskGetTickCount();
|
||||||
|
|
||||||
|
// this is a little risk here but that avoids calling scheduler if we are cached
|
||||||
if (now - expander->age >= pdMS_TO_TICKS(age)) {
|
if (now - expander->age >= pdMS_TO_TICKS(age)) {
|
||||||
|
if (xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)) == pdFALSE) return -1;
|
||||||
|
|
||||||
expander->shadow = expander->model->read(&expander->phy);
|
expander->shadow = expander->model->read(&expander->phy);
|
||||||
expander->age = now;
|
expander->age = now;
|
||||||
|
|
||||||
|
xSemaphoreGive(expander->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow);
|
ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow);
|
||||||
return (expander->shadow >> gpio) & 0x01;
|
return (expander->shadow >> gpio) & 0x01;
|
||||||
@@ -199,34 +239,47 @@ int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander) {
|
|||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Set GPIO level with cache
|
* Set GPIO level with cache
|
||||||
*/
|
*/
|
||||||
void gpio_exp_set_level(int gpio, int level, struct gpio_exp_s *expander) {
|
esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, struct gpio_exp_s *expander) {
|
||||||
if ((expander = find_expander(expander, &gpio)) == NULL) return;
|
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
|
||||||
uint32_t mask = 1 << gpio;
|
uint32_t mask = 1 << gpio;
|
||||||
|
|
||||||
|
// limited risk with lack of semaphore here
|
||||||
if ((expander->w_mask & mask) == 0) {
|
if ((expander->w_mask & mask) == 0) {
|
||||||
ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio);
|
ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio);
|
||||||
return;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
level = level ? mask : 0;
|
if (direct) {
|
||||||
mask &= expander->shadow;
|
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
|
||||||
|
|
||||||
// only write if shadow not up to date
|
level = level ? mask : 0;
|
||||||
if ((mask ^ level) && expander->model->write) {
|
mask &= expander->shadow;
|
||||||
expander->shadow = (expander->shadow & ~(mask | level)) | level;
|
|
||||||
expander->model->write(&expander->phy, expander->r_mask, expander->shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow);
|
// only write if shadow not up to date
|
||||||
|
if ((mask ^ level) && expander->model->write) {
|
||||||
|
expander->shadow = (expander->shadow & ~(mask | level)) | level;
|
||||||
|
expander->model->write(&expander->phy, expander->r_mask, expander->shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
xSemaphoreGive(expander->mutex);
|
||||||
|
ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow);
|
||||||
|
} else {
|
||||||
|
async_request_t request = { .gpio = gpio, .level = level, .type = ASYNC_WRITE, .expander = expander };
|
||||||
|
if (xQueueSend(async_queue, &request, 0) == pdFALSE) return ESP_ERR_INVALID_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Set GPIO pullmode
|
* Set GPIO pullmode
|
||||||
*/
|
*/
|
||||||
void gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander) {
|
esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander) {
|
||||||
if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) {
|
if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) {
|
||||||
expander->model->set_pull_mode(gpio, mode);
|
expander->model->set_pull_mode(gpio, mode);
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
@@ -237,7 +290,9 @@ void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expan
|
|||||||
uint8_t clz;
|
uint8_t clz;
|
||||||
|
|
||||||
// memorize newly read value and just update if requested
|
// memorize newly read value and just update if requested
|
||||||
|
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50));
|
||||||
expander->shadow ^= value;
|
expander->shadow ^= value;
|
||||||
|
xSemaphoreGive(expander->mutex);
|
||||||
if (!enumerator) return;
|
if (!enumerator) return;
|
||||||
|
|
||||||
// now we have a bitmap of all modified GPIO sinnce last call
|
// now we have a bitmap of all modified GPIO sinnce last call
|
||||||
@@ -248,6 +303,29 @@ void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Wrapper function
|
||||||
|
*/
|
||||||
|
esp_err_t gpio_set_pull_mode_u(int gpio, gpio_pull_mode_t mode) {
|
||||||
|
if (gpio < GPIO_EXP_BASE_MIN) return gpio_set_pull_mode(gpio, mode);
|
||||||
|
return gpio_exp_set_pull_mode(gpio, mode, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t gpio_set_direction_u(int gpio, gpio_mode_t mode) {
|
||||||
|
if (gpio < GPIO_EXP_BASE_MIN) return gpio_set_direction(gpio, mode);
|
||||||
|
return gpio_exp_set_direction(gpio, mode, NULL) ? ESP_OK : ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gpio_get_level_u(int gpio) {
|
||||||
|
if (gpio < GPIO_EXP_BASE_MIN) return gpio_get_level(gpio);
|
||||||
|
return gpio_exp_get_level(gpio, 50, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t gpio_set_level_u(int gpio, int level) {
|
||||||
|
if (gpio < GPIO_EXP_BASE_MIN) return gpio_set_level(gpio, level);
|
||||||
|
return gpio_exp_set_level(gpio, level, false, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* Find the expander related to base
|
* Find the expander related to base
|
||||||
*/
|
*/
|
||||||
@@ -282,7 +360,7 @@ static void pca9535_write(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t s
|
|||||||
*/
|
*/
|
||||||
static void pca85xx_set_direction(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t w_mask) {
|
static void pca85xx_set_direction(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t w_mask) {
|
||||||
// all inputs must be set to 1 (open drain) and output are left open as well
|
// all inputs must be set to 1 (open drain) and output are left open as well
|
||||||
i2c_write_word(phy->port, phy->addr, 0x255, r_mask | w_mask);
|
i2c_write_word(phy->port, phy->addr, 0xff, r_mask | w_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int pca85xx_read(union gpio_exp_phy_u *phy) {
|
static int pca85xx_read(union gpio_exp_phy_u *phy) {
|
||||||
@@ -311,6 +389,29 @@ static void IRAM_ATTR intr_isr_handler(void* arg)
|
|||||||
ESP_EARLY_LOGD(TAG, "INTR for expander %u", expander->first);
|
ESP_EARLY_LOGD(TAG, "INTR for expander %u", expander->first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* Async task
|
||||||
|
*/
|
||||||
|
void async_handler(void *arg) {
|
||||||
|
while (1) {
|
||||||
|
esp_err_t err;
|
||||||
|
async_request_t request;
|
||||||
|
|
||||||
|
if (!xQueueReceive(async_queue, &request, portMAX_DELAY)) continue;
|
||||||
|
|
||||||
|
switch (request.type) {
|
||||||
|
case ASYNC_WRITE:
|
||||||
|
err = gpio_exp_set_level(request.gpio, request.level, true, request.expander);
|
||||||
|
if (err != ESP_OK) ESP_LOGW(TAG, "Can't execute async GPIO %d write request (%d)", request.gpio, err);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -43,10 +43,16 @@ struct gpio_exp_s* gpio_exp_expander(int gpio);
|
|||||||
/* For all functions below when <expander> is provided, GPIO's can be numbered from 0. If <expander>
|
/* 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 */
|
is NULL, then GPIO must start from base */
|
||||||
struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander);
|
struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander);
|
||||||
void gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander);
|
esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander);
|
||||||
int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander);
|
int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander);
|
||||||
void gpio_exp_set_level(int gpio, int level, struct gpio_exp_s *expander);
|
esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, struct gpio_exp_s *expander);
|
||||||
|
|
||||||
/* This can be called to enumerate modified GPIO since last read. Note that <enumerator>
|
/* This can be called to enumerate modified GPIO since last read. Note that <enumerator>
|
||||||
can be NULL to initialize all GPIOs */
|
can be NULL to initialize all GPIOs */
|
||||||
void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expander);
|
void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expander);
|
||||||
|
|
||||||
|
// option to use either built-in or expanded GPIO
|
||||||
|
esp_err_t gpio_set_direction_u(int gpio, gpio_mode_t mode);
|
||||||
|
esp_err_t gpio_set_pull_mode_u(int gpio, gpio_pull_mode_t mode);
|
||||||
|
int gpio_get_level_u(int gpio);
|
||||||
|
esp_err_t gpio_set_level_u(int gpio, int level);
|
||||||
|
|||||||
@@ -370,10 +370,11 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
|||||||
|
|
||||||
// memory still used but at least task is not created
|
// memory still used but at least task is not created
|
||||||
if (stats) {
|
if (stats) {
|
||||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
// we allocate TCB but stack is staic to avoid SPIRAM fragmentation
|
||||||
|
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||||
static EXT_RAM_ATTR StackType_t xStack[STAT_STACK_SIZE] __attribute__ ((aligned (4)));
|
static EXT_RAM_ATTR StackType_t xStack[STAT_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||||
stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE,
|
stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE,
|
||||||
NULL, ESP_TASK_PRIO_MIN, xStack, &xTaskBuffer);
|
NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user