powerpods/main/bosch456.c
simon 47c75110c9 Stream slave accel via ESP-NOW with master snapshot cache.
Slaves push BMA456 samples at 16ms when enabled; the master caches per
client and exposes ACCEL_SNAPSHOT and ACCEL_STREAM over UART. goTool adds
dashboard stream controls, HTTP accel-stream routes, and an external
WebSocket API with per-connection receive/interval and slave stream commands.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 19:11:36 +02:00

401 lines
10 KiB
C

/**
* BMA456H integration for Powerpod (ESP-IDF I2C master + Bosch SensorAPI).
*
* Polls accelerometer at 10 Hz; tap events arrive on BMA456_INT_GPIO.
* Accel logging is filtered in software (deadzone); slaves stream samples via ESP-NOW.
*/
#include "bosch456.h"
#include "bma4.h"
#include "bma4_defs.h"
#include "bma456h.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/idf_additions.h"
#include "freertos/semphr.h"
#include <rom/ets_sys.h>
#include <string.h>
static const char *TAG = "[BMA456]";
#define BMA4_READ_WRITE_LEN UINT8_C(46)
#define BMA4_I2C_MAX_WRITE (1u + BMA4_READ_WRITE_LEN)
#define SENSOR_POLL_MS 100
static i2c_master_dev_handle_t s_bma456_dev;
static bool s_bma456_ready;
static struct bma4_dev s_bma456;
static uint32_t s_accel_deadzone = BMA456_DEFAULT_ACCEL_DEADZONE;
static int16_t s_last_x;
static int16_t s_last_y;
static int16_t s_last_z;
static bool s_have_last_sample;
static volatile bool s_int_pending;
static SemaphoreHandle_t s_accel_mutex;
static esp_err_t check_bma4(const char *api_name, int8_t rslt);
/* Bosch SensorAPI platform hooks (intf_ptr → i2c_master_dev_handle_t *). */
BMA4_INTF_RET_TYPE bma4_i2c_read(uint8_t reg_addr, uint8_t *reg_data,
uint32_t len, void *intf_ptr) {
if (s_bma456_dev == NULL) {
return BMA4_E_COM_FAIL;
}
esp_err_t err = i2c_master_transmit_receive(s_bma456_dev, &reg_addr, 1,
reg_data, len, -1);
return (err == ESP_OK) ? BMA4_OK : BMA4_E_COM_FAIL;
}
BMA4_INTF_RET_TYPE bma4_i2c_write(uint8_t reg_addr, const uint8_t *reg_data,
uint32_t len, void *intf_ptr) {
(void)intf_ptr;
if (s_bma456_dev == NULL || reg_data == NULL) {
return BMA4_E_COM_FAIL;
}
if (len > BMA4_READ_WRITE_LEN) {
return BMA4_E_COM_FAIL;
}
uint8_t buffer[BMA4_I2C_MAX_WRITE];
buffer[0] = reg_addr;
memcpy(&buffer[1], reg_data, len);
esp_err_t err =
i2c_master_transmit(s_bma456_dev, buffer, (size_t)(len + 1), -1);
return (err == ESP_OK) ? BMA4_OK : BMA4_E_COM_FAIL;
}
void bma4_delay_us(uint32_t period, void *intf_ptr) {
(void)intf_ptr;
uint32_t wait_ms = period / 1000;
uint32_t wait_us = period % 1000;
if (wait_ms > 0) {
vTaskDelay(pdMS_TO_TICKS(wait_ms));
}
if (wait_us > 0) {
ets_delay_us(wait_us);
}
}
void bma4_error_codes_print_result(const char api_name[], int8_t rslt) {
if (rslt == BMA4_OK) {
return;
}
ESP_LOGW(TAG, "%s failed: %d", api_name, (int)rslt);
}
static esp_err_t check_bma4(const char *api_name, int8_t rslt) {
if (rslt == BMA4_OK) {
return ESP_OK;
}
bma4_error_codes_print_result(api_name, rslt);
return ESP_FAIL;
}
static int16_t axis_delta(int16_t a, int16_t b) {
int32_t d = (int32_t)a - (int32_t)b;
return (int16_t)(d < 0 ? -d : d);
}
static bool sample_exceeds_deadzone(int16_t x, int16_t y, int16_t z) {
if (!s_have_last_sample) {
return true;
}
return axis_delta(x, s_last_x) > (int16_t)s_accel_deadzone ||
axis_delta(y, s_last_y) > (int16_t)s_accel_deadzone ||
axis_delta(z, s_last_z) > (int16_t)s_accel_deadzone;
}
bool bma456_is_ready(void) { return s_bma456_ready; }
void bma456_set_accel_deadzone(uint32_t deadzone_lsb) {
s_accel_deadzone = deadzone_lsb;
s_have_last_sample = false;
if (s_bma456_ready) {
ESP_LOGI(TAG, "accel deadzone %lu LSB", (unsigned long)deadzone_lsb);
}
}
uint32_t bma456_get_accel_deadzone(void) { return s_accel_deadzone; }
esp_err_t bma456_read_accel(int16_t *x, int16_t *y, int16_t *z) {
if (!s_bma456_ready || x == NULL || y == NULL || z == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (s_accel_mutex == NULL ||
xSemaphoreTake(s_accel_mutex, pdMS_TO_TICKS(500)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
struct bma4_accel sens_data = {0};
int8_t ret = bma4_read_accel_xyz(&sens_data, &s_bma456);
xSemaphoreGive(s_accel_mutex);
if (ret != BMA4_OK) {
bma4_error_codes_print_result("bma4_read_accel_xyz", ret);
return ESP_FAIL;
}
*x = sens_data.x;
*y = sens_data.y;
*z = sens_data.z;
return ESP_OK;
}
void bma456_report_accel_if_changed(int16_t x, int16_t y, int16_t z) {
if (!s_bma456_ready || !sample_exceeds_deadzone(x, y, z)) {
return;
}
s_last_x = x;
s_last_y = y;
s_last_z = z;
s_have_last_sample = true;
ESP_LOGI(TAG, "ACC X=%d Y=%d Z=%d (deadzone %lu)", x, y, z,
(unsigned long)s_accel_deadzone);
}
static void IRAM_ATTR bma456_int_isr(void *arg) {
(void)arg;
s_int_pending = true;
}
static void handle_tap_interrupt(void) {
uint16_t int_status = 0;
int8_t ret = bma456h_read_int_status(&int_status, &s_bma456);
if (ret != BMA4_OK) {
bma4_error_codes_print_result("bma456h_read_int_status", ret);
return;
}
struct bma456h_out_state tap_out = {0};
ret = bma456h_output_state(&tap_out, &s_bma456);
if (ret != BMA4_OK) {
bma4_error_codes_print_result("bma456h_output_state", ret);
return;
}
if (tap_out.single_tap) {
ESP_LOGI(TAG, "tap: single");
} else if (tap_out.double_tap) {
ESP_LOGI(TAG, "tap: double");
} else if (tap_out.triple_tap) {
ESP_LOGI(TAG, "tap: triple");
}
}
static void remove_bma456_device(void) {
if (s_bma456_ready) {
gpio_isr_handler_remove(BMA456_INT_GPIO);
}
if (s_bma456_dev != NULL) {
i2c_master_bus_rm_device(s_bma456_dev);
s_bma456_dev = NULL;
}
s_bma456_ready = false;
s_int_pending = false;
}
static void read_sensor_task(void *param) {
(void)param;
if (!s_bma456_ready) {
vTaskDelete(NULL);
return;
}
struct bma4_accel sens_data = {0};
while (1) {
bool got_sample = false;
if (s_accel_mutex != NULL &&
xSemaphoreTake(s_accel_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {
int8_t ret = bma4_read_accel_xyz(&sens_data, &s_bma456);
xSemaphoreGive(s_accel_mutex);
if (ret == BMA4_OK) {
got_sample = true;
} else {
bma4_error_codes_print_result("bma4_read_accel_xyz", ret);
}
}
if (got_sample) {
bma456_report_accel_if_changed(sens_data.x, sens_data.y, sens_data.z);
}
if (s_int_pending) {
s_int_pending = false;
handle_tap_interrupt();
}
vTaskDelay(pdMS_TO_TICKS(SENSOR_POLL_MS));
}
}
static esp_err_t configure_tap_interrupt(void) {
esp_err_t err;
int8_t ret;
const uint8_t int_line = BMA4_INTR2_MAP;
struct bma456h_multitap_settings tap_settings = {0};
ret = bma456h_tap_get_parameter(&tap_settings, &s_bma456);
if (check_bma4("bma456h_tap_get_parameter", ret) != ESP_OK) {
return ESP_FAIL;
}
tap_settings.tap_sens_thres = 0;
ret = bma456h_tap_set_parameter(&tap_settings, &s_bma456);
if (check_bma4("bma456h_tap_set_parameter", ret) != ESP_OK) {
return ESP_FAIL;
}
ret = bma456h_feature_enable(
(BMA456H_SINGLE_TAP_EN | BMA456H_DOUBLE_TAP_EN | BMA456H_TRIPLE_TAP_EN),
BMA4_ENABLE, &s_bma456);
if (check_bma4("bma456h_feature_enable", ret) != ESP_OK) {
return ESP_FAIL;
}
ret = bma456h_map_interrupt(int_line, BMA456H_TAP_OUT_INT, BMA4_ENABLE,
&s_bma456);
if (check_bma4("bma456h_map_interrupt", ret) != ESP_OK) {
return ESP_FAIL;
}
struct bma4_int_pin_config pin_config = {0};
ret = bma4_get_int_pin_config(&pin_config, int_line, &s_bma456);
if (check_bma4("bma4_get_int_pin_config", ret) != ESP_OK) {
return ESP_FAIL;
}
pin_config.edge_ctrl = BMA4_EDGE_TRIGGER;
pin_config.output_en = BMA4_OUTPUT_ENABLE;
pin_config.lvl = BMA4_ACTIVE_HIGH;
pin_config.od = BMA4_PUSH_PULL;
pin_config.input_en = BMA4_INPUT_DISABLE;
ret = bma4_set_int_pin_config(&pin_config, int_line, &s_bma456);
if (check_bma4("bma4_set_int_pin_config", ret) != ESP_OK) {
return ESP_FAIL;
}
gpio_reset_pin(BMA456_INT_GPIO);
gpio_set_direction(BMA456_INT_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode(BMA456_INT_GPIO, GPIO_PULLDOWN_ONLY);
gpio_set_intr_type(BMA456_INT_GPIO, GPIO_INTR_POSEDGE);
err = gpio_install_isr_service(0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
return err;
}
err = gpio_isr_handler_add(BMA456_INT_GPIO, bma456_int_isr, NULL);
if (err != ESP_OK) {
return err;
}
return ESP_OK;
}
esp_err_t init_bma456(i2c_master_bus_handle_t bus_handle) {
int8_t ret;
esp_err_t err;
s_bma456_ready = false;
s_bma456_dev = NULL;
s_int_pending = false;
if (bus_handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = BMA456_I2C_ADDR,
.scl_speed_hz = 100000,
};
err = i2c_master_bus_add_device(bus_handle, &dev_cfg, &s_bma456_dev);
if (err != ESP_OK) {
return err;
}
s_bma456.intf = BMA4_I2C_INTF;
s_bma456.bus_read = bma4_i2c_read;
s_bma456.bus_write = bma4_i2c_write;
s_bma456.delay_us = bma4_delay_us;
s_bma456.read_write_len = BMA4_READ_WRITE_LEN;
s_bma456.intf_ptr = &s_bma456_dev;
s_bma456.chip_id = 0;
ret = bma456h_init(&s_bma456);
if (check_bma4("bma456h_init", ret) != ESP_OK) {
remove_bma456_device();
return ESP_ERR_NOT_FOUND;
}
ESP_LOGI(TAG, "chip id 0x%02x", s_bma456.chip_id);
ret = bma4_soft_reset(&s_bma456);
if (check_bma4("bma4_soft_reset", ret) != ESP_OK) {
goto fail;
}
vTaskDelay(pdMS_TO_TICKS(20));
ret = bma4_set_advance_power_save(BMA4_DISABLE, &s_bma456);
if (check_bma4("bma4_set_advance_power_save", ret) != ESP_OK) {
goto fail;
}
vTaskDelay(pdMS_TO_TICKS(10));
ret = bma456h_write_config_file(&s_bma456);
if (check_bma4("bma456h_write_config_file", ret) != ESP_OK) {
goto fail;
}
struct bma4_accel_config accel_config = {0};
ret = bma4_get_accel_config(&accel_config, &s_bma456);
if (check_bma4("bma4_get_accel_config", ret) != ESP_OK) {
goto fail;
}
accel_config.range = BMA4_ACCEL_RANGE_2G;
ret = bma4_set_accel_config(&accel_config, &s_bma456);
if (check_bma4("bma4_set_accel_config", ret) != ESP_OK) {
goto fail;
}
ret = bma4_set_accel_enable(BMA4_ENABLE, &s_bma456);
if (check_bma4("bma4_set_accel_enable", ret) != ESP_OK) {
goto fail;
}
if (configure_tap_interrupt() != ESP_OK) {
goto fail;
}
if (s_accel_mutex == NULL) {
s_accel_mutex = xSemaphoreCreateMutex();
if (s_accel_mutex == NULL) {
goto fail;
}
}
if (xTaskCreate(read_sensor_task, "bma456_poll", 4096, NULL, 1, NULL) !=
pdPASS) {
goto fail;
}
s_bma456_ready = true;
ESP_LOGI(TAG, "ready (I2C 0x%02x, INT GPIO%d, poll %d ms)", BMA456_I2C_ADDR,
BMA456_INT_GPIO, SENSOR_POLL_MS);
return ESP_OK;
fail:
remove_bma456_device();
return ESP_FAIL;
}