357 lines
9.8 KiB
C
357 lines
9.8 KiB
C
#include <string.h>
|
|
|
|
#include "driver/i2c_master.h"
|
|
#include "ahtxx.h"
|
|
#include "esp_check.h"
|
|
#include "esp_log.h"
|
|
#include "esp_timer.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/semphr.h"
|
|
#include "freertos/task.h"
|
|
#include "i2c_master_messager.h"
|
|
|
|
static const char *TAG = "i2c_master_messager";
|
|
#define BH1750_REINIT_INTERVAL_MS (3000)
|
|
#define AHT30_REINIT_INTERVAL_MS (3000)
|
|
|
|
typedef struct {
|
|
bool initialized;
|
|
bool owns_i2c_bus;
|
|
bool bh1750_ready;
|
|
bool aht30_ready;
|
|
i2c_master_messager_config_t config;
|
|
i2c_master_bus_handle_t i2c_bus;
|
|
bh1750_handle_t bh1750;
|
|
ahtxx_handle_t aht30;
|
|
i2c_master_messager_data_t data;
|
|
SemaphoreHandle_t lock;
|
|
TaskHandle_t task_handle;
|
|
} i2c_master_messager_ctx_t;
|
|
|
|
static i2c_master_messager_ctx_t g_ctx;
|
|
|
|
static esp_err_t i2c_master_messager_try_init_bh1750(void)
|
|
{
|
|
if (!g_ctx.config.bh1750_enable) {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (g_ctx.bh1750 != NULL) {
|
|
bh1750_delete(g_ctx.bh1750);
|
|
g_ctx.bh1750 = NULL;
|
|
}
|
|
|
|
esp_err_t ret = bh1750_create(g_ctx.i2c_bus, BH1750_I2C_ADDRESS_DEFAULT, &g_ctx.bh1750);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
ret = bh1750_power_on(g_ctx.bh1750);
|
|
if (ret != ESP_OK) {
|
|
bh1750_delete(g_ctx.bh1750);
|
|
g_ctx.bh1750 = NULL;
|
|
return ret;
|
|
}
|
|
|
|
ret = bh1750_set_measure_mode(g_ctx.bh1750, g_ctx.config.bh1750_mode);
|
|
if (ret != ESP_OK) {
|
|
bh1750_delete(g_ctx.bh1750);
|
|
g_ctx.bh1750 = NULL;
|
|
return ret;
|
|
}
|
|
|
|
g_ctx.bh1750_ready = true;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t i2c_master_messager_try_init_aht30(void)
|
|
{
|
|
if (!g_ctx.config.aht30_enable) {
|
|
return ESP_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (g_ctx.aht30 != NULL) {
|
|
ahtxx_delete(g_ctx.aht30);
|
|
g_ctx.aht30 = NULL;
|
|
}
|
|
|
|
ahtxx_config_t aht_cfg = I2C_AHT30_CONFIG_DEFAULT;
|
|
esp_err_t ret = ahtxx_init(g_ctx.i2c_bus, &aht_cfg, &g_ctx.aht30);
|
|
if (ret != ESP_OK) {
|
|
return ret;
|
|
}
|
|
|
|
g_ctx.aht30_ready = true;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void i2c_master_messager_task(void *arg)
|
|
{
|
|
(void)arg;
|
|
int64_t last_bh1750_reinit_ms = 0;
|
|
int64_t last_bh1750_read_ms = 0;
|
|
int64_t last_aht30_read_ms = 0;
|
|
int64_t last_aht30_reinit_ms = 0;
|
|
while (1) {
|
|
int64_t now_ms = esp_timer_get_time() / 1000;
|
|
|
|
if (g_ctx.config.bh1750_enable && !g_ctx.bh1750_ready &&
|
|
(now_ms - last_bh1750_reinit_ms) >= BH1750_REINIT_INTERVAL_MS) {
|
|
last_bh1750_reinit_ms = now_ms;
|
|
esp_err_t init_ret = i2c_master_messager_try_init_bh1750();
|
|
if (init_ret == ESP_OK) {
|
|
ESP_LOGI(TAG, "BH1750 reinit success");
|
|
} else {
|
|
ESP_LOGW(TAG, "BH1750 reinit failed: %s", esp_err_to_name(init_ret));
|
|
if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
g_ctx.data.bh1750.valid = false;
|
|
g_ctx.data.bh1750.last_error = init_ret;
|
|
xSemaphoreGive(g_ctx.lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_ctx.bh1750_ready && g_ctx.bh1750 != NULL &&
|
|
(now_ms - last_bh1750_read_ms) >= g_ctx.config.bh1750_read_period_ms) {
|
|
float lux = 0.0f;
|
|
esp_err_t bh1750_ret = bh1750_get_data(g_ctx.bh1750, &lux);
|
|
last_bh1750_read_ms = now_ms;
|
|
|
|
if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
g_ctx.data.bh1750.valid = (bh1750_ret == ESP_OK);
|
|
g_ctx.data.bh1750.last_error = bh1750_ret;
|
|
if (bh1750_ret == ESP_OK) {
|
|
g_ctx.data.bh1750.lux = lux;
|
|
g_ctx.data.bh1750.last_update_ms = now_ms;
|
|
}
|
|
xSemaphoreGive(g_ctx.lock);
|
|
}
|
|
|
|
if (bh1750_ret != ESP_OK) {
|
|
ESP_LOGW(TAG, "bh1750_get_data failed: %s", esp_err_to_name(bh1750_ret));
|
|
}
|
|
}
|
|
|
|
if (g_ctx.config.aht30_enable && !g_ctx.aht30_ready &&
|
|
(now_ms - last_aht30_reinit_ms) >= AHT30_REINIT_INTERVAL_MS) {
|
|
last_aht30_reinit_ms = now_ms;
|
|
esp_err_t init_ret = i2c_master_messager_try_init_aht30();
|
|
if (init_ret == ESP_OK) {
|
|
ESP_LOGI(TAG, "AHT30 reinit success");
|
|
} else {
|
|
ESP_LOGW(TAG, "AHT30 reinit failed: %s", esp_err_to_name(init_ret));
|
|
if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
g_ctx.data.aht30.valid = false;
|
|
g_ctx.data.aht30.last_error = init_ret;
|
|
xSemaphoreGive(g_ctx.lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_ctx.aht30_ready && g_ctx.aht30 != NULL &&
|
|
(now_ms - last_aht30_read_ms) >= g_ctx.config.aht30_read_period_ms) {
|
|
float temperature_c = 0.0f;
|
|
float humidity_rh = 0.0f;
|
|
esp_err_t aht30_ret = ahtxx_get_measurement(g_ctx.aht30, &temperature_c, &humidity_rh);
|
|
last_aht30_read_ms = now_ms;
|
|
|
|
if (xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
g_ctx.data.aht30.valid = (aht30_ret == ESP_OK);
|
|
g_ctx.data.aht30.last_error = aht30_ret;
|
|
if (aht30_ret == ESP_OK) {
|
|
g_ctx.data.aht30.temperature_c = temperature_c;
|
|
g_ctx.data.aht30.humidity_rh = humidity_rh;
|
|
g_ctx.data.aht30.last_update_ms = now_ms;
|
|
}
|
|
xSemaphoreGive(g_ctx.lock);
|
|
}
|
|
|
|
if (aht30_ret != ESP_OK) {
|
|
ESP_LOGW(TAG, "ahtxx_get_measurement failed: %s", esp_err_to_name(aht30_ret));
|
|
if (aht30_ret == ESP_ERR_INVALID_STATE || aht30_ret == ESP_ERR_TIMEOUT) {
|
|
if (g_ctx.aht30 != NULL) {
|
|
ahtxx_delete(g_ctx.aht30);
|
|
g_ctx.aht30 = NULL;
|
|
}
|
|
g_ctx.aht30_ready = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(I2C_MASTER_MESSAGER_MIN_PERIOD_MS));
|
|
}
|
|
}
|
|
|
|
esp_err_t i2c_master_messager_init(const i2c_master_messager_config_t *config)
|
|
{
|
|
ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is null");
|
|
ESP_RETURN_ON_FALSE(config->bh1750_enable || config->aht30_enable,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"at least one sensor must be enabled");
|
|
ESP_RETURN_ON_FALSE(!config->bh1750_enable ||
|
|
config->bh1750_read_period_ms >= I2C_MASTER_MESSAGER_MIN_PERIOD_MS,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"bh1750_read_period_ms too small");
|
|
ESP_RETURN_ON_FALSE(!config->aht30_enable ||
|
|
config->aht30_read_period_ms >= I2C_MASTER_MESSAGER_MIN_PERIOD_MS,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"aht30_read_period_ms too small");
|
|
|
|
if (g_ctx.initialized) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
memset(&g_ctx, 0, sizeof(g_ctx));
|
|
g_ctx.config = *config;
|
|
|
|
g_ctx.lock = xSemaphoreCreateMutex();
|
|
ESP_RETURN_ON_FALSE(g_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "failed to create mutex");
|
|
|
|
const i2c_master_bus_config_t bus_cfg = {
|
|
.i2c_port = config->i2c_port,
|
|
.sda_io_num = config->sda_io_num,
|
|
.scl_io_num = config->scl_io_num,
|
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
|
.glitch_ignore_cnt = 7,
|
|
.flags.enable_internal_pullup = config->i2c_enable_internal_pullup,
|
|
};
|
|
|
|
esp_err_t ret = i2c_new_master_bus(&bus_cfg, &g_ctx.i2c_bus);
|
|
if (ret != ESP_OK) {
|
|
if (ret == ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGW(TAG,
|
|
"i2c port %d already initialized, trying to reuse existing master bus",
|
|
config->i2c_port);
|
|
ret = i2c_master_get_bus_handle(config->i2c_port, &g_ctx.i2c_bus);
|
|
if (ret != ESP_OK || g_ctx.i2c_bus == NULL) {
|
|
ESP_LOGE(TAG,
|
|
"failed to reuse i2c bus on port %d: %s",
|
|
config->i2c_port,
|
|
esp_err_to_name(ret));
|
|
vSemaphoreDelete(g_ctx.lock);
|
|
g_ctx.lock = NULL;
|
|
return (ret == ESP_OK) ? ESP_ERR_INVALID_STATE : ret;
|
|
}
|
|
g_ctx.owns_i2c_bus = false;
|
|
} else {
|
|
ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(ret));
|
|
vSemaphoreDelete(g_ctx.lock);
|
|
g_ctx.lock = NULL;
|
|
return ret;
|
|
}
|
|
} else {
|
|
g_ctx.owns_i2c_bus = true;
|
|
}
|
|
|
|
if (!config->bh1750_enable) {
|
|
g_ctx.data.bh1750.valid = false;
|
|
g_ctx.data.bh1750.last_error = ESP_ERR_NOT_SUPPORTED;
|
|
} else {
|
|
g_ctx.data.bh1750.valid = false;
|
|
g_ctx.data.bh1750.last_error = ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
if (!config->aht30_enable) {
|
|
g_ctx.data.aht30.valid = false;
|
|
g_ctx.data.aht30.last_error = ESP_ERR_NOT_SUPPORTED;
|
|
} else {
|
|
g_ctx.data.aht30.valid = false;
|
|
g_ctx.data.aht30.last_error = ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
g_ctx.initialized = true;
|
|
ESP_LOGI(TAG,
|
|
"initialized: port=%d scl=%d sda=%d pullup_int=%d bh1750(en=%d,ready=%d,default,%ums) aht30(en=%d,ready=%d,default,%ums)",
|
|
config->i2c_port,
|
|
config->scl_io_num,
|
|
config->sda_io_num,
|
|
config->i2c_enable_internal_pullup,
|
|
config->bh1750_enable,
|
|
g_ctx.bh1750_ready,
|
|
config->bh1750_read_period_ms,
|
|
config->aht30_enable,
|
|
g_ctx.aht30_ready,
|
|
config->aht30_read_period_ms);
|
|
ESP_LOGI(TAG,
|
|
"i2c bus is ready; sensor drivers will initialize lazily in task loop");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t i2c_master_messager_start(void)
|
|
{
|
|
ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized");
|
|
if (g_ctx.task_handle != NULL) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
BaseType_t ok = xTaskCreate(i2c_master_messager_task,
|
|
"i2c_msg_task",
|
|
4096,
|
|
NULL,
|
|
5,
|
|
&g_ctx.task_handle);
|
|
ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "failed to create task");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t i2c_master_messager_stop(void)
|
|
{
|
|
ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized");
|
|
if (g_ctx.task_handle != NULL) {
|
|
vTaskDelete(g_ctx.task_handle);
|
|
g_ctx.task_handle = NULL;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t i2c_master_messager_get_data(i2c_master_messager_data_t *out_data)
|
|
{
|
|
ESP_RETURN_ON_FALSE(g_ctx.initialized, ESP_ERR_INVALID_STATE, TAG, "not initialized");
|
|
ESP_RETURN_ON_FALSE(out_data != NULL, ESP_ERR_INVALID_ARG, TAG, "out_data is null");
|
|
ESP_RETURN_ON_FALSE(g_ctx.lock != NULL, ESP_ERR_INVALID_STATE, TAG, "lock not ready");
|
|
|
|
ESP_RETURN_ON_FALSE(xSemaphoreTake(g_ctx.lock, pdMS_TO_TICKS(100)) == pdTRUE,
|
|
ESP_ERR_TIMEOUT,
|
|
TAG,
|
|
"failed to lock shared data");
|
|
*out_data = g_ctx.data;
|
|
xSemaphoreGive(g_ctx.lock);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t i2c_master_messager_deinit(void)
|
|
{
|
|
if (!g_ctx.initialized) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
i2c_master_messager_stop();
|
|
|
|
if (g_ctx.bh1750 != NULL) {
|
|
bh1750_delete(g_ctx.bh1750);
|
|
g_ctx.bh1750 = NULL;
|
|
}
|
|
|
|
if (g_ctx.aht30 != NULL) {
|
|
ahtxx_delete(g_ctx.aht30);
|
|
g_ctx.aht30 = NULL;
|
|
}
|
|
|
|
if (g_ctx.i2c_bus != NULL && g_ctx.owns_i2c_bus) {
|
|
i2c_del_master_bus(g_ctx.i2c_bus);
|
|
}
|
|
g_ctx.i2c_bus = NULL;
|
|
g_ctx.owns_i2c_bus = false;
|
|
|
|
if (g_ctx.lock != NULL) {
|
|
vSemaphoreDelete(g_ctx.lock);
|
|
g_ctx.lock = NULL;
|
|
}
|
|
|
|
memset(&g_ctx, 0, sizeof(g_ctx));
|
|
return ESP_OK;
|
|
}
|