跳转至

ADXL355 代码

组件架构

- driver
    - adxl355
        - include
            - adxl355.h
        - adxl355.c
        - CMakeLists.txt

driver/adxl355/CMakeLists.txt

set(src_dirs
    .
)

set(include_dirs
    include
)

set(requires
    driver
    spi
)

idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

adxl355.h

/**
 * @file adxl355.h
 * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
 * @brief ADXL355 3-axis accelerometer driver
 * @version 1.0
 * @date 2025-08-23
 * @copyright Copyright (c) 2025
 *
 */

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "driver/spi_master.h"

//==============================================================================
// GPIO Pin Definitions
//==============================================================================

/// @brief ADXL355 CS (Chip Select) GPIO pin
#define ADXL355_CS_GPIO_PIN GPIO_NUM_4

//==============================================================================
// Constants (exactly like plasmapper)
//==============================================================================

/// @brief Default SPI frequency
#define ADXL355_DEFAULT_SPI_FREQUENCY    10000000

/// @brief Temperature intercept, LSB
#define ADXL355_TEMPERATURE_INTERCEPT_LSB    1885

/// @brief Temperature intercept, °C
#define ADXL355_TEMPERATURE_INTERCEPT_DEG_C  25.0f

/// @brief Temperature slope, LSB/°C
#define ADXL355_TEMPERATURE_SLOPE            -9.05f

/// @brief Acceleration scale factor for ±2 g range, g/LSB
#define ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_2G  3.9e-6f

/// @brief Acceleration scale factor for ±4 g range, g/LSB
#define ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_4G  7.8e-6f

/// @brief Acceleration scale factor for ±8 g range, g/LSB
#define ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_8G  15.6e-6f

/// @brief Maximum number of the FIFO samples
#define ADXL355_MAX_NUMBER_OF_FIFO_SAMPLES   96

//==============================================================================
// SPI Configuration (exactly like plasmapper)
//==============================================================================

/// @brief SPI mode (8 = Mode 0 in ESP-IDF)
#define ADXL355_SPI_MODE                    8

/// @brief Number of address bits
#define ADXL355_SPI_NUMBER_OF_ADDRESS_BITS  8

/// @brief Max SCLK-MISO delay in ns
#define ADXL355_SPI_MAX_SCLK_MISO_DELAY    30

//==============================================================================
// Register Addresses (exactly like plasmapper)
//==============================================================================

#define ADXL355_REG_DEVID_AD        0x00
#define ADXL355_REG_DEVID_MST       0x01
#define ADXL355_REG_PARTID          0x02
#define ADXL355_REG_REVID           0x03
#define ADXL355_REG_STATUS          0x04
#define ADXL355_REG_FIFO_ENTRIES    0x05
#define ADXL355_REG_TEMP2           0x06
#define ADXL355_REG_TEMP1           0x07
#define ADXL355_REG_XDATA3          0x08
#define ADXL355_REG_XDATA2          0x09
#define ADXL355_REG_XDATA1          0x0A
#define ADXL355_REG_YDATA3          0x0B
#define ADXL355_REG_YDATA2          0x0C
#define ADXL355_REG_YDATA1          0x0D
#define ADXL355_REG_ZDATA3          0x0E
#define ADXL355_REG_ZDATA2          0x0F
#define ADXL355_REG_ZDATA1          0x10
#define ADXL355_REG_FIFO_DATA       0x11
#define ADXL355_REG_OFFSET_X_H      0x1E
#define ADXL355_REG_OFFSET_X_L      0x1F
#define ADXL355_REG_OFFSET_Y_H      0x20
#define ADXL355_REG_OFFSET_Y_L      0x21
#define ADXL355_REG_OFFSET_Z_H      0x22
#define ADXL355_REG_OFFSET_Z_L      0x23
#define ADXL355_REG_ACT_EN          0x24
#define ADXL355_REG_ACT_THRESH_H    0x25
#define ADXL355_REG_ACT_THRESH_L    0x26
#define ADXL355_REG_ACT_COUNT       0x27
#define ADXL355_REG_FILTER          0x28
#define ADXL355_REG_FIFO_SAMPLES    0x29
#define ADXL355_REG_INT_MAP         0x2A
#define ADXL355_REG_SYNC            0x2B
#define ADXL355_REG_RANGE           0x2C
#define ADXL355_REG_POWER_CTL       0x2D
#define ADXL355_REG_SELF_TEST       0x2E
#define ADXL355_REG_RESET           0x2F

//==============================================================================
// Register Bit Masks (exactly like plasmapper)
//==============================================================================

#define ADXL355_REG_STATUS_DATA_RDY      0x01
#define ADXL355_REG_STATUS_FIFO_FULL     0x02
#define ADXL355_REG_STATUS_FIFO_OVR      0x04
#define ADXL355_REG_STATUS_ACTIVITY      0x08
#define ADXL355_REG_STATUS_NVM_BUSY      0x10

#define ADXL355_REG_FILTER_HPF_SHIFT     4
#define ADXL355_REG_FILTER_HPF_MASK      0xF0
#define ADXL355_REG_FILTER_ODR_MASK      0x0F

#define ADXL355_REG_SYNC_SYNC_MAP        0x03
#define ADXL355_REG_SYNC_EXT_CLK         0x04

#define ADXL355_REG_RANGE_RANGE_MASK     0x03
#define ADXL355_REG_RANGE_RANGE_PM2G     0x01
#define ADXL355_REG_RANGE_RANGE_PM4G     0x02
#define ADXL355_REG_RANGE_RANGE_PM8G     0x03
#define ADXL355_REG_RANGE_INT_POL_SHIFT  6
#define ADXL355_REG_RANGE_INT_POL_MASK   0x40
#define ADXL355_REG_RANGE_I2C_HS_SHIFT   7
#define ADXL355_REG_RANGE_I2C_HS_MASK    0x80

#define ADXL355_REG_POWER_CTL_STANDBY   0x01
#define ADXL355_REG_POWER_CTL_TEMP_OFF  0x02
#define ADXL355_REG_POWER_CTL_DRDY_OFF  0x04

#define ADXL355_REG_SELF_TEST_ST1       0x01
#define ADXL355_REG_SELF_TEST_ST2       0x02

#define ADXL355_REG_RESET_RESET_CODE    0x52

//==============================================================================
// Enums (exactly like plasmapper)
//==============================================================================

/// @brief ADXL355 status
typedef enum {
    ADXL355_STATUS_DATA_RDY = 0x01,      ///< Data ready
    ADXL355_STATUS_FIFO_FULL = 0x02,     ///< FIFO full
    ADXL355_STATUS_FIFO_OVR = 0x04,      ///< FIFO overrun
    ADXL355_STATUS_ACTIVITY = 0x08,      ///< Activity detected
    ADXL355_STATUS_NVM_BUSY = 0x10       ///< NVM busy
} adxl355_status_t;

/// @brief ADXL355 high-pass filter frequency
typedef enum {
    ADXL355_HPF_NONE = 0x00,             ///< High-pass filter disabled
    ADXL355_HPF_24_7 = 0x01,             ///< ODR*24.700e-4
    ADXL355_HPF_6_2084 = 0x02,          ///< ODR*6.2084e-4
    ADXL355_HPF_1_5545 = 0x03,          ///< ODR*1.5545e-4
    ADXL355_HPF_0_3862 = 0x04,          ///< ODR*0.3862e-4
    ADXL355_HPF_0_0954 = 0x05,          ///< ODR*0.0954e-4
    ADXL355_HPF_0_0238 = 0x06           ///< ODR*0.0238e-4
} adxl355_hpf_frequency_t;

/// @brief ADXL355 output data rate
typedef enum {
    ADXL355_ODR_4000 = 0x00,             ///< 4000 Hz
    ADXL355_ODR_2000 = 0x01,             ///< 2000 Hz
    ADXL355_ODR_1000 = 0x02,             ///< 1000 Hz
    ADXL355_ODR_500 = 0x03,              ///< 500 Hz
    ADXL355_ODR_250 = 0x04,              ///< 250 Hz
    ADXL355_ODR_125 = 0x05,              ///< 125 Hz
    ADXL355_ODR_62_5 = 0x06,             ///< 62.5 Hz
    ADXL355_ODR_31_25 = 0x07,            ///< 31.25 Hz
    ADXL355_ODR_15_625 = 0x08,           ///< 15.625 Hz
    ADXL355_ODR_7_813 = 0x09,            ///< 7.813 Hz
    ADXL355_ODR_3_906 = 0x0A             ///< 3.906 Hz
} adxl355_output_data_rate_t;

/// @brief ADXL355 acceleration range
typedef enum {
    ADXL355_RANGE_2G = 0x01,             ///< ±2 g
    ADXL355_RANGE_4G = 0x02,             ///< ±4 g
    ADXL355_RANGE_8G = 0x03              ///< ±8 g
} adxl355_range_t;

/// @brief ADXL355 interrupt polarity
typedef enum {
    ADXL355_INT_POL_ACTIVE_LOW = 0x00,   ///< Active low
    ADXL355_INT_POL_ACTIVE_HIGH = 0x01   ///< Active high
} adxl355_interrupt_polarity_t;

/// @brief ADXL355 I2C speed
typedef enum {
    ADXL355_I2C_SPEED_FAST = 0x00,       ///< Fast
    ADXL355_I2C_SPEED_HIGH_SPEED = 0x01  ///< High speed
} adxl355_i2c_speed_t;

/// @brief ADXL355 synchronization
typedef enum {
    ADXL355_SYNC_INTERNAL = 0x00,        ///< Internal
    ADXL355_SYNC_EXTERNAL = 0x01,        ///< External
    ADXL355_SYNC_EXTERNAL_WITH_INTERPOLATION = 0x02  ///< External with interpolation
} adxl355_synchronization_t;

//==============================================================================
// Data Structures (exactly like plasmapper)
//==============================================================================

/// @brief ADXL355 device information
typedef struct {
    uint8_t vendor_id;                   ///< Vendor ID
    uint8_t family_id;                   ///< Device family ID
    uint8_t device_id;                   ///< Device ID
    uint8_t revision_id;                 ///< Revision ID
} adxl355_device_info_t;

/// @brief ADXL355 raw accelerations
typedef struct {
    int32_t x;                           ///< Raw X-axis acceleration
    int32_t y;                           ///< Raw Y-axis acceleration
    int32_t z;                           ///< Raw Z-axis acceleration
} adxl355_raw_accelerations_t;

/// @brief ADXL355 accelerations in g
typedef struct {
    float x;                              ///< X-axis acceleration, g
    float y;                              ///< Y-axis acceleration, g
    float z;                              ///< Z-axis acceleration, g
} adxl355_accelerations_t;

/// @brief ADXL355 handle
typedef struct {
    spi_device_handle_t spi_handle;       ///< SPI device handle
    adxl355_range_t range;                ///< Current range setting
    adxl355_output_data_rate_t odr;       ///< Current ODR setting
    float scale_factor;                   ///< Current scale factor
} adxl355_handle_t;

//==============================================================================
// Function Declarations
//==============================================================================

/**
 * @brief Initialize ADXL355 (including SPI device configuration)
 * @param handle ADXL355 handle
 * @param range Initial range setting
 * @param odr Initial output data rate
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_init(adxl355_handle_t *handle, adxl355_range_t range, adxl355_output_data_rate_t odr);

/**
 * @brief Deinitialize ADXL355
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_deinit(adxl355_handle_t *handle);

/**
 * @brief Reset ADXL355 device
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_reset(adxl355_handle_t *handle);

/**
 * @brief Read device information
 * @param handle ADXL355 handle
 * @param device_info Device information output
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_device_info(adxl355_handle_t *handle, adxl355_device_info_t *device_info);

/**
 * @brief Read device status
 * @param handle ADXL355 handle
 * @param status Status output
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_status(adxl355_handle_t *handle, adxl355_status_t *status);

/**
 * @brief Read raw accelerations
 * @param handle ADXL355 handle
 * @param raw_accel Raw accelerations output
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_raw_accelerations(adxl355_handle_t *handle, adxl355_raw_accelerations_t *raw_accel);

/**
 * @brief Read accelerations in g
 * @param handle ADXL355 handle
 * @param accel Accelerations output in g
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_accelerations(adxl355_handle_t *handle, adxl355_accelerations_t *accel);

/**
 * @brief Read raw temperature
 * @param handle ADXL355 handle
 * @param raw_temp Raw temperature output
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_raw_temperature(adxl355_handle_t *handle, uint16_t *raw_temp);

/**
 * @brief Read temperature in °C
 * @param handle ADXL355 handle
 * @param temp Temperature output in °C
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_temperature(adxl355_handle_t *handle, float *temp);

/**
 * @brief Set acceleration range
 * @param handle ADXL355 handle
 * @param range Range setting
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_set_range(adxl355_handle_t *handle, adxl355_range_t range);

/**
 * @brief Read acceleration range
 * @param handle ADXL355 handle
 * @param range Range output
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_range(adxl355_handle_t *handle, adxl355_range_t *range);

/**
 * @brief Set output data rate
 * @param handle ADXL355 handle
 * @param odr Output data rate
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_set_odr(adxl355_handle_t *handle, adxl355_output_data_rate_t odr);

/**
 * @brief Read output data rate
 * @param handle ADXL355 handle
 * @param odr Output data rate output
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_odr(adxl355_handle_t *handle, adxl355_output_data_rate_t *odr);

/**
 * @brief Enable measurement mode
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_enable_measurement(adxl355_handle_t *handle);

/**
 * @brief Disable measurement mode (standby)
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_disable_measurement(adxl355_handle_t *handle);

/**
 * @brief Check if measurement is enabled
 * @param handle ADXL355 handle
 * @param is_enabled Output: true if measurement enabled
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_is_measurement_enabled(adxl355_handle_t *handle, bool *is_enabled);

/**
 * @brief Enable temperature processing
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_enable_temperature(adxl355_handle_t *handle);

/**
 * @brief Disable temperature processing
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_disable_temperature(adxl355_handle_t *handle);

/**
 * @brief Check if temperature is enabled
 * @param handle ADXL355 handle
 * @param is_enabled Output: true if temperature enabled
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_is_temperature_enabled(adxl355_handle_t *handle, bool *is_enabled);

/**
 * @brief Enable data ready output
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_enable_data_ready(adxl355_handle_t *handle);

/**
 * @brief Disable data ready output
 * @param handle ADXL355 handle
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_disable_data_ready(adxl355_handle_t *handle);

/**
 * @brief Check if data ready is enabled
 * @param handle ADXL355 handle
 * @param is_enabled Output: true if data ready enabled
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_is_data_ready_enabled(adxl355_handle_t *handle, bool *is_enabled);

/**
 * @brief Read acceleration scale factor
 * @param handle ADXL355 handle
 * @param scale_factor Scale factor output
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_read_acceleration_scale_factor(adxl355_handle_t *handle, float *scale_factor);

/**
 * @brief ADXL355 specific: Write command then read data
 * @param handle ADXL355 handle
 * @param cmd Command byte to send
 * @param data Buffer to store received data
 * @param len Length of data to read
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_spi_read(adxl355_handle_t *handle, uint8_t cmd, uint8_t *data, int len);

#ifdef __cplusplus
}
#endif

adxl355.c

/**
 * @file adxl355.c
 * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
 * @brief ADXL355 3-axis accelerometer driver implementation
 * @version 1.0
 * @date 2025-08-23
 * @copyright Copyright (c) 2025
 *
 */

#include "adxl355.h"
#include "spi.h"
#include "esp_log.h"
#include "esp_check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

//==============================================================================

static const char* TAG = "ADXL355";

//==============================================================================

/* Private function declarations */
static esp_err_t adxl355_configure_spi_device(spi_device_handle_t *device_handle);
static esp_err_t adxl355_read_register(adxl355_handle_t *handle, uint8_t reg, uint8_t *value);
static esp_err_t adxl355_read_registers(adxl355_handle_t *handle, uint8_t reg, uint8_t *data, uint8_t len);
static esp_err_t adxl355_write_register(adxl355_handle_t *handle, uint8_t reg, uint8_t value);
static esp_err_t adxl355_write_registers(adxl355_handle_t *handle, uint8_t reg, const uint8_t *data, uint8_t len);
static const char* adxl355_get_range_string(adxl355_range_t range);
static const char* adxl355_get_odr_string(adxl355_output_data_rate_t odr);

//==============================================================================

esp_err_t adxl355_init(adxl355_handle_t *handle, adxl355_range_t range, adxl355_output_data_rate_t odr)
{
    esp_err_t ret;

    ESP_LOGI(TAG, "=== ADXL355 INITIALIZATION START ===");
    ESP_LOGI(TAG, "Initialization parameters: range=%s, ODR=%s", 
             adxl355_get_range_string(range), adxl355_get_odr_string(odr));

    if (handle == NULL) {
        ESP_LOGE(TAG, "Invalid argument: handle=%p", (void*)handle);
        return ESP_ERR_INVALID_ARG;
    }

    /* Step 1: Configure SPI device for ADXL355 */
    ESP_LOGI(TAG, "Configuring SPI device for ADXL355...");
    ret = adxl355_configure_spi_device(&handle->spi_handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to configure SPI device: %s", esp_err_to_name(ret));
        return ret;
    }
    ESP_LOGI(TAG, "SPI device configured successfully");

    /* Initialize handle */
    handle->range = range;
    handle->odr = odr;

    /* Set initial scale factor based on range */
    ESP_LOGI(TAG, "Setting scale factor for range %s...", adxl355_get_range_string(range));
    switch (range) {
        case ADXL355_RANGE_2G:
            handle->scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_2G;
            ESP_LOGI(TAG, "Range %s selected, scale factor: %.2e g/LSB", 
                     adxl355_get_range_string(range), handle->scale_factor);
            break;
        case ADXL355_RANGE_4G:
            handle->scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_4G;
            ESP_LOGI(TAG, "Range %s selected, scale factor: %.2e g/LSB", 
                     adxl355_get_range_string(range), handle->scale_factor);
            break;
        case ADXL355_RANGE_8G:
            handle->scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_8G;
            ESP_LOGI(TAG, "Range %s selected, scale factor: %.2e g/LSB", 
                     adxl355_get_range_string(range), handle->scale_factor);
            break;
        default:
            ESP_LOGE(TAG, "Invalid range setting: %d", range);
            return ESP_ERR_INVALID_ARG;
    }

    /* Reset device */
    ESP_LOGI(TAG, "Resetting ADXL355 device...");
    ret = adxl355_reset(handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to reset device: %s", esp_err_to_name(ret));
        return ret;
    }
    ESP_LOGI(TAG, "Device reset completed");

    /* Wait for reset to complete */
    vTaskDelay(pdMS_TO_TICKS(100));

    /* Read device info to verify communication */
    ESP_LOGI(TAG, "Reading device information...");
    adxl355_device_info_t device_info;
    ret = adxl355_read_device_info(handle, &device_info);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read device info: %s", esp_err_to_name(ret));
        return ret;
    }

    ESP_LOGI(TAG, "Device ID: 0x%02X, Vendor ID: 0x%02X", device_info.device_id, device_info.vendor_id);

    /* Set range and ODR */
    ESP_LOGI(TAG, "Setting range to %s...", adxl355_get_range_string(range));
    ret = adxl355_set_range(handle, range);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set range: %s", esp_err_to_name(ret));
        return ret;
    }
    ESP_LOGI(TAG, "Range set successfully");

    ESP_LOGI(TAG, "Setting ODR to %s...", adxl355_get_odr_string(odr));
    ret = adxl355_set_odr(handle, odr);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set ODR: %s", esp_err_to_name(ret));
        return ret;
    }
    ESP_LOGI(TAG, "ODR set successfully");

    /* Enable measurement mode */
    ESP_LOGI(TAG, "Enabling measurement mode...");
    ret = adxl355_enable_measurement(handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable measurement: %s", esp_err_to_name(ret));
        return ret;
    }
    ESP_LOGI(TAG, "Measurement mode enabled");

    /* Enable temperature processing */
    ESP_LOGI(TAG, "Enabling temperature processing...");
    ret = adxl355_enable_temperature(handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable temperature: %s", esp_err_to_name(ret));
        return ret;
    }
    ESP_LOGI(TAG, "Temperature processing enabled");

    ESP_LOGI(TAG, "=== ADXL355 INITIALIZATION COMPLETED SUCCESSFULLY ===");
    ESP_LOGI(TAG, "Final configuration: range=%s, ODR=%s, scale_factor=%.2e g/LSB", 
             adxl355_get_range_string(handle->range), adxl355_get_odr_string(handle->odr), handle->scale_factor);
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_deinit(adxl355_handle_t *handle)
{
    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Disable measurement mode */
    esp_err_t ret = adxl355_disable_measurement(handle);
    if (ret != ESP_OK) {
        ESP_LOGW(TAG, "Failed to disable measurement during deinit");
    }

    /* Clear handle */
    handle->spi_handle = NULL;
    handle->range = 0;
    handle->odr = 0;
    handle->scale_factor = 0.0f;

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_reset(adxl355_handle_t *handle)
{
    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Send reset command */
    esp_err_t ret = adxl355_write_register(handle, ADXL355_REG_RESET, ADXL355_REG_RESET_RESET_CODE);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to send reset command");
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_device_info(adxl355_handle_t *handle, adxl355_device_info_t *device_info)
{
    esp_err_t ret;
    uint8_t data[4];

    if (handle == NULL || device_info == NULL) {
        ESP_LOGE(TAG, "Invalid arguments: handle=%p, device_info=%p", (void*)handle, (void*)device_info);
        return ESP_ERR_INVALID_ARG;
    }

    /* Read 4 bytes starting from DEVID_AD register */
    ret = adxl355_read_registers(handle, ADXL355_REG_DEVID_AD, data, 4);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read device info registers: %s", esp_err_to_name(ret));
        return ret;
    }

    /* Extract device information (exactly like plasmapper) */
    device_info->vendor_id = data[0];
    device_info->family_id = data[1];
    device_info->device_id = data[2];
    device_info->revision_id = data[3];

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_status(adxl355_handle_t *handle, adxl355_status_t *status)
{
    if (handle == NULL || status == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    uint8_t status_reg;
    esp_err_t ret = adxl355_read_register(handle, ADXL355_REG_STATUS, &status_reg);
    if (ret != ESP_OK) {
        return ret;
    }

    *status = (adxl355_status_t)status_reg;
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_raw_accelerations(adxl355_handle_t *handle, adxl355_raw_accelerations_t *raw_accel)
{
    esp_err_t ret;
    uint8_t data[9];

    if (handle == NULL || raw_accel == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read 9 bytes starting from XDATA3 register */
    ret = adxl355_read_registers(handle, ADXL355_REG_XDATA3, data, 9);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read acceleration data");
        return ret;
    }

    /* Initialize to 0 (exactly like plasmapper) */
    raw_accel->x = 0;
    raw_accel->y = 0;
    raw_accel->z = 0;

    /* Reconstruct 20-bit values (exactly like plasmapper) */
    /* X-axis: data[2], data[1], data[0] */
    ((uint8_t*)&raw_accel->x)[1] = data[2];
    ((uint8_t*)&raw_accel->x)[2] = data[1];
    ((uint8_t*)&raw_accel->x)[3] = data[0];

    /* Y-axis: data[5], data[4], data[3] */
    ((uint8_t*)&raw_accel->y)[1] = data[5];
    ((uint8_t*)&raw_accel->y)[2] = data[4];
    ((uint8_t*)&raw_accel->y)[3] = data[3];

    /* Z-axis: data[8], data[7], data[6] */
    ((uint8_t*)&raw_accel->z)[1] = data[8];
    ((uint8_t*)&raw_accel->z)[2] = data[7];
    ((uint8_t*)&raw_accel->z)[3] = data[6];

    /* Convert to 20-bit signed values (divide by 4096) */
    raw_accel->x /= 4096;
    raw_accel->y /= 4096;
    raw_accel->z /= 4096;

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_accelerations(adxl355_handle_t *handle, adxl355_accelerations_t *accel)
{
    esp_err_t ret;
    adxl355_raw_accelerations_t raw_accel;
    float scale_factor;

    if (handle == NULL || accel == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read raw accelerations */
    ret = adxl355_read_raw_accelerations(handle, &raw_accel);
    if (ret != ESP_OK) {
        return ret;
    }

    /* Get current scale factor */
    ret = adxl355_read_acceleration_scale_factor(handle, &scale_factor);
    if (ret != ESP_OK) {
        return ret;
    }

    /* Convert to g units */
    accel->x = raw_accel.x * scale_factor;
    accel->y = raw_accel.y * scale_factor;
    accel->z = raw_accel.z * scale_factor;

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_raw_temperature(adxl355_handle_t *handle, uint16_t *raw_temp)
{
    esp_err_t ret;
    uint8_t data[2];

    if (handle == NULL || raw_temp == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read 2 bytes starting from TEMP2 register */
    ret = adxl355_read_registers(handle, ADXL355_REG_TEMP2, data, 2);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read temperature data");
        return ret;
    }

    /* Reconstruct 16-bit value (exactly like plasmapper) */
    ((uint8_t*)raw_temp)[0] = data[1];
    ((uint8_t*)raw_temp)[1] = data[0];

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_temperature(adxl355_handle_t *handle, float *temp)
{
    esp_err_t ret;
    uint16_t raw_temp;

    if (handle == NULL || temp == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read raw temperature */
    ret = adxl355_read_raw_temperature(handle, &raw_temp);
    if (ret != ESP_OK) {
        return ret;
    }

    /* Convert to °C using plasmapper's formula */
    *temp = (((int16_t)raw_temp) - ((int16_t)ADXL355_TEMPERATURE_INTERCEPT_LSB)) / 
             ADXL355_TEMPERATURE_SLOPE + ADXL355_TEMPERATURE_INTERCEPT_DEG_C;

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_set_range(adxl355_handle_t *handle, adxl355_range_t range)
{
    esp_err_t ret;
    uint8_t range_register;

    if (handle == NULL) {
        ESP_LOGE(TAG, "Invalid handle: NULL");
        return ESP_ERR_INVALID_ARG;
    }

    ESP_LOGI(TAG, "Setting ADXL355 range to %s...", adxl355_get_range_string(range));

    /* Read current range register value like plasmapper */
    ret = adxl355_read_register(handle, ADXL355_REG_RANGE, &range_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read range register: %s", esp_err_to_name(ret));
        return ret;
    }

    /* Clear range bits (bits 0-1) like plasmapper */
    range_register &= ~ADXL355_REG_RANGE_RANGE_MASK;

    /* Set new range value */
    uint8_t new_range_value = range & ADXL355_REG_RANGE_RANGE_MASK;
    range_register |= new_range_value;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_RANGE, range_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to write range register: %s", esp_err_to_name(ret));
        return ret;
    }

    /* Verify the write by reading back */
    uint8_t verify_register;
    ret = adxl355_read_register(handle, ADXL355_REG_RANGE, &verify_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to verify range register: %s", esp_err_to_name(ret));
        return ret;
    }

    if (verify_register != range_register) {
        ESP_LOGE(TAG, "Range register verification failed! Expected: 0x%02X, Got: 0x%02X", 
                 range_register, verify_register);
        return ESP_ERR_INVALID_STATE;
    }

    /* Update scale factor */
    float old_scale_factor = handle->scale_factor;
    switch (range) {
        case ADXL355_RANGE_2G:
            handle->scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_2G;
            break;
        case ADXL355_RANGE_4G:
            handle->scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_4G;
            break;
        case ADXL355_RANGE_8G:
            handle->scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_8G;
            break;
        default:
            ESP_LOGE(TAG, "Invalid range value: %d", range);
            return ESP_ERR_INVALID_ARG;
    }

    handle->range = range;
    ESP_LOGI(TAG, "Range changed from %.2e to %s, scale factor: %.2e g/LSB", 
             old_scale_factor, adxl355_get_range_string(range), handle->scale_factor);
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_range(adxl355_handle_t *handle, adxl355_range_t *range)
{
    esp_err_t ret;
    uint8_t range_register;

    if (handle == NULL || range == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    ret = adxl355_read_register(handle, ADXL355_REG_RANGE, &range_register);
    if (ret != ESP_OK) {
        return ret;
    }

    *range = (adxl355_range_t)(range_register & ADXL355_REG_RANGE_RANGE_MASK);
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_set_odr(adxl355_handle_t *handle, adxl355_output_data_rate_t odr)
{
    esp_err_t ret;
    uint8_t filter_reg;

    if (handle == NULL) {
        ESP_LOGE(TAG, "Invalid handle: NULL");
        return ESP_ERR_INVALID_ARG;
    }

    ESP_LOGI(TAG, "Setting ADXL355 ODR to %s...", adxl355_get_odr_string(odr));

    /* Read current filter register value */
    ret = adxl355_read_register(handle, ADXL355_REG_FILTER, &filter_reg);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read filter register: %s", esp_err_to_name(ret));
        return ret;
    }

    /* Clear ODR bits and set new value (like plasmapper) */
    uint8_t old_odr = filter_reg & ADXL355_REG_FILTER_ODR_MASK;
    filter_reg &= ~ADXL355_REG_FILTER_ODR_MASK;

    uint8_t new_odr_value = odr & ADXL355_REG_FILTER_ODR_MASK;
    filter_reg |= new_odr_value;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_FILTER, filter_reg);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to write filter register: %s", esp_err_to_name(ret));
        return ret;
    }

    /* Verify the write by reading back */
    uint8_t verify_register;
    ret = adxl355_read_register(handle, ADXL355_REG_FILTER, &verify_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to verify filter register: %s", esp_err_to_name(ret));
        return ret;
    }

    if (verify_register != filter_reg) {
        ESP_LOGE(TAG, "Filter register verification failed! Expected: 0x%02X, Got: 0x%02X", 
                 filter_reg, verify_register);
        return ESP_ERR_INVALID_STATE;
    }

    handle->odr = odr;
    ESP_LOGI(TAG, "ODR changed from %s to %s", 
             adxl355_get_odr_string(old_odr), adxl355_get_odr_string(odr));
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_odr(adxl355_handle_t *handle, adxl355_output_data_rate_t *odr)
{
    esp_err_t ret;
    uint8_t filter_reg;

    if (handle == NULL || odr == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    ret = adxl355_read_register(handle, ADXL355_REG_FILTER, &filter_reg);
    if (ret != ESP_OK) {
        return ret;
    }

    *odr = (adxl355_output_data_rate_t)(filter_reg & ADXL355_REG_FILTER_ODR_MASK);
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_enable_measurement(adxl355_handle_t *handle)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read current power control register value */
    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read power control register");
        return ret;
    }

    /* Clear standby bit to enable measurement */
    power_ctl_register &= ~ADXL355_REG_POWER_CTL_STANDBY;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_POWER_CTL, power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable measurement");
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_disable_measurement(adxl355_handle_t *handle)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read current power control register value */
    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read power control register");
        return ret;
    }

    /* Set standby bit to disable measurement */
    power_ctl_register |= ADXL355_REG_POWER_CTL_STANDBY;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_POWER_CTL, power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to disable measurement");
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_is_measurement_enabled(adxl355_handle_t *handle, bool *is_enabled)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL || is_enabled == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        return ret;
    }

    *is_enabled = !(power_ctl_register & ADXL355_REG_POWER_CTL_STANDBY);
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_enable_temperature(adxl355_handle_t *handle)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read current power control register value */
    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read power control register");
        return ret;
    }

    /* Clear temperature off bit to enable temperature processing */
    power_ctl_register &= ~ADXL355_REG_POWER_CTL_TEMP_OFF;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_POWER_CTL, power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable temperature");
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_disable_temperature(adxl355_handle_t *handle)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read current power control register value */
    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read power control register");
        return ret;
    }

    /* Set temperature off bit to disable temperature processing */
    power_ctl_register |= ADXL355_REG_POWER_CTL_TEMP_OFF;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_POWER_CTL, power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to disable temperature");
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_is_temperature_enabled(adxl355_handle_t *handle, bool *is_enabled)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL || is_enabled == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        return ret;
    }

    *is_enabled = !(power_ctl_register & ADXL355_REG_POWER_CTL_TEMP_OFF);
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_enable_data_ready(adxl355_handle_t *handle)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read current power control register value */
    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read power control register");
        return ret;
    }

    /* Clear data ready off bit to enable data ready output */
    power_ctl_register &= ~ADXL355_REG_POWER_CTL_DRDY_OFF;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_POWER_CTL, power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable data ready");
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_disable_data_ready(adxl355_handle_t *handle)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Read current power control register value */
    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read power control register");
        return ret;
    }

    /* Set data ready off bit to disable data ready output */
    power_ctl_register |= ADXL355_REG_POWER_CTL_DRDY_OFF;

    /* Write updated register value */
    ret = adxl355_write_register(handle, ADXL355_REG_POWER_CTL, power_ctl_register);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to disable data ready");
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_is_data_ready_enabled(adxl355_handle_t *handle, bool *is_enabled)
{
    esp_err_t ret;
    uint8_t power_ctl_register;

    if (handle == NULL || is_enabled == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    ret = adxl355_read_register(handle, ADXL355_REG_POWER_CTL, &power_ctl_register);
    if (ret != ESP_OK) {
        return ret;
    }

    *is_enabled = !(power_ctl_register & ADXL355_REG_POWER_CTL_DRDY_OFF);
    return ESP_OK;
}

//==============================================================================

esp_err_t adxl355_read_acceleration_scale_factor(adxl355_handle_t *handle, float *scale_factor)
{
    esp_err_t ret;
    adxl355_range_t range;

    if (handle == NULL || scale_factor == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    ret = adxl355_read_range(handle, &range);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to read range");
        return ret;
    }

    switch (range) {
        case ADXL355_RANGE_2G:
            *scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_2G;
            break;
        case ADXL355_RANGE_4G:
            *scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_4G;
            break;
        case ADXL355_RANGE_8G:
            *scale_factor = ADXL355_ACCELERATION_SCALE_FACTOR_RANGE_8G;
            break;
        default:
            *scale_factor = 1.0f;
            break;
    }

    return ESP_OK;
}

//==============================================================================
// Private Functions
//==============================================================================

/**
 * @brief Configure and add ADXL355 SPI device to SPI3 bus
 * @param device_handle Output parameter, returns SPI device handle
 * @return ESP_OK on success, error code on failure
 */
static esp_err_t adxl355_configure_spi_device(spi_device_handle_t *device_handle)
{
    esp_err_t ret;
    spi_device_interface_config_t dev_config = {0};

    /* Configure ADXL355-specific SPI device parameters */
    dev_config.clock_speed_hz = 10000000;          // 10 MHz (ADXL355 maximum supported)
    dev_config.mode = 0;                            // SPI Mode 0 (CPOL=0, CPHA=0)
    dev_config.spics_io_num = ADXL355_CS_GPIO_PIN; // CS pin
    dev_config.queue_size = 1;                      // Single transaction queue
    dev_config.command_bits = 0;                    // No command bits
    dev_config.address_bits = 8;                    // 8-bit address
    dev_config.input_delay_ns = 30;                 // 30ns delay
    dev_config.flags = 0;                           // Default flags (full-duplex)

    /* Add SPI device to bus */
    ret = spi_bus_add_device(SPI3_HOST, &dev_config, device_handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to add ADXL355 device to SPI3 bus: %s", esp_err_to_name(ret));
        return ret;
    }

    ESP_LOGI(TAG, "ADXL355 SPI device configured successfully");
    return ESP_OK;
}

//==============================================================================

/**
 * @brief Read single register
 * @param handle ADXL355 handle
 * @param reg Register address
 * @param value Output value
 * @return ESP_OK on success, error code on failure
 */
static esp_err_t adxl355_read_register(adxl355_handle_t *handle, uint8_t reg, uint8_t *value)
{
    ESP_LOGD(TAG, "READ_REG: reg=0x%02X, handle=%p, value_ptr=%p", reg, (void*)handle, (void*)value);

    if (handle == NULL || value == NULL) {
        ESP_LOGE(TAG, "READ_REG: Invalid arguments - handle=%p, value=%p", (void*)handle, (void*)value);
        return ESP_ERR_INVALID_ARG;
    }

    /* Left shift address by 1 and set LSB to 1 for read (exactly like plasmapper) */
    uint8_t read_cmd = (reg << 1) | 0x01;
    ESP_LOGD(TAG, "READ_REG: reg=0x%02X, shifted=0x%02X, read_cmd=0x%02X", reg, reg << 1, read_cmd);

    /* Use SPI transaction: write command then read data */
    spi_transaction_t t = {0};
    t.cmd = 0;                                        /* No command bits */
    t.addr = read_cmd;                                /* Address with read bit set */
    t.length = 8;                                     /* 1 byte = 8 bits */
    t.tx_buffer = NULL;                               /* No write data */
    t.rx_buffer = value;                              /* Read data buffer */
    t.flags = 0;                                      /* Use default flags */

    ESP_LOGD(TAG, "READ_REG: SPI transaction - addr=0x%02X, length=%d bits", t.addr, t.length);

    esp_err_t ret = spi_device_polling_transmit(handle->spi_handle, &t);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "READ_REG: SPI read transaction failed: %s", esp_err_to_name(ret));
        return ret;
    }

    ESP_LOGD(TAG, "READ_REG: Success - reg=0x%02X, value=0x%02X", reg, *value);
    return ESP_OK;
}

//==============================================================================

/**
 * @brief Read multiple registers
 * @param handle ADXL355 handle
 * @param reg Starting register address
 * @param data Output data buffer
 * @param len Number of bytes to read
 * @return ESP_OK on success, error code on failure
 */
static esp_err_t adxl355_read_registers(adxl355_handle_t *handle, uint8_t reg, uint8_t *data, uint8_t len)
{
    if (handle == NULL || data == NULL || len == 0) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Left shift address by 1 and set LSB to 1 for read (exactly like plasmapper) */
    uint8_t read_cmd = (reg << 1) | 0x01;

    /* Use the new ADXL355-specific SPI read function */
    return adxl355_spi_read(handle, read_cmd, data, len);
}

//==============================================================================

/**
 * @brief Write single register
 * @param handle ADXL355 handle
 * @param reg Register address
 * @param value Value to write
 * @return ESP_OK on success, error code on failure
 */
static esp_err_t adxl355_write_register(adxl355_handle_t *handle, uint8_t reg, uint8_t value)
{
    ESP_LOGD(TAG, "WRITE_REG: reg=0x%02X, value=0x%02X, handle=%p", reg, value, (void*)handle);

    if (handle == NULL) {
        ESP_LOGE(TAG, "WRITE_REG: Invalid handle - NULL");
        return ESP_ERR_INVALID_ARG;
    }

    /* Left shift address by 1 (exactly like plasmapper) */
    uint8_t write_cmd = reg << 1;
    ESP_LOGD(TAG, "WRITE_REG: reg=0x%02X, shifted=0x%02X, write_cmd=0x%02X", reg, reg << 1, write_cmd);

    /* Use SPI transaction: write command and data */
    spi_transaction_t t = {0};
    t.cmd = 0;                                        /* No command bits */
    t.addr = write_cmd;                               /* Address with write bit clear */
    t.length = 8;                                     /* 1 byte = 8 bits */
    t.tx_buffer = &value;                             /* Write data */
    t.rx_buffer = NULL;                               /* No read data */
    t.flags = 0;                                      /* Use default flags */

    ESP_LOGD(TAG, "WRITE_REG: SPI transaction - addr=0x%02X, length=%d bits, data=0x%02X", 
             t.addr, t.length, value);

    esp_err_t ret = spi_device_polling_transmit(handle->spi_handle, &t);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "WRITE_REG: SPI write transaction failed: %s", esp_err_to_name(ret));
        return ret;
    }

    ESP_LOGD(TAG, "WRITE_REG: Success - reg=0x%02X, value=0x%02X", reg, value);
    return ESP_OK;
}

//==============================================================================

/**
 * @brief Write multiple registers
 * @param handle ADXL355 handle
 * @param reg Starting register address
 * @param data Data to write
 * @param len Number of bytes to write
 * @return ESP_OK on success, error code on failure
 */
static esp_err_t adxl355_write_registers(adxl355_handle_t *handle, uint8_t reg, const uint8_t *data, uint8_t len)
{
    if (handle == NULL || data == NULL || len == 0) {
        return ESP_ERR_INVALID_ARG;
    }

    /* Left shift address by 1 (exactly like plasmapper) */
    uint8_t write_cmd = reg << 1;

    /* Use SPI transaction: write command and data */
    spi_transaction_t t = {0};
    t.cmd = 0;                                        /* No command bits */
    t.addr = write_cmd;                               /* Address with write bit clear */
    t.length = len * 8;                               /* Data size in bits */
    t.tx_buffer = data;                               /* Write data */
    t.rx_buffer = NULL;                               /* No read data */
    t.flags = 0;                                      /* Use default flags */

    esp_err_t ret = spi_device_polling_transmit(handle->spi_handle, &t);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "SPI write registers transaction failed: %s", esp_err_to_name(ret));
        return ret;
    }

    return ESP_OK;
}

//==============================================================================

/**
 * @brief ADXL355 specific: Write command then read data
 * @param handle SPI handle
 * @param cmd Command byte to send
 * @param data Buffer to store received data
 * @param len Length of data to read
 * @return ESP_OK on success, error code on failure
 */
esp_err_t adxl355_spi_read(adxl355_handle_t *handle, uint8_t cmd, uint8_t *data, int len)
{
    if (handle == NULL || data == NULL || len <= 0) {
        ESP_LOGE(TAG, "Invalid arguments for ADXL355 SPI read");
        return ESP_ERR_INVALID_ARG;
    }

    /* Configure transaction exactly like plasmapper */
    /* command = 0, address = cmd, writeData = NULL, readData = data, dataSize = len * 8 */
    spi_transaction_t t = {0};
    t.cmd = 0;                                        /* No command bits */
    t.addr = cmd;                                     /* Address (already formatted) */
    t.length = len * 8;                               /* Data size in bits */
    t.tx_buffer = NULL;                               /* No write data */
    t.rx_buffer = data;                               /* Read data buffer */
    t.flags = 0;                                      /* Use default flags */

    /* Use polling transmit like plasmapper */
    esp_err_t ret = spi_device_polling_transmit(handle->spi_handle, &t);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "ADXL355 SPI read failed: %s", esp_err_to_name(ret));
        return ret;
    }

    return ESP_OK;
}

/**
 * @brief Get string representation of range setting
 * @param range Range setting
 * @return String representation
 */
static const char* adxl355_get_range_string(adxl355_range_t range)
{
    switch (range) {
        case ADXL355_RANGE_2G:
            return "±2G";
        case ADXL355_RANGE_4G:
            return "±4G";
        case ADXL355_RANGE_8G:
            return "±8G";
        default:
            return "Unknown";
    }
}

/**
 * @brief Get string representation of ODR setting
 * @param odr ODR setting
 * @return String representation
 */
static const char* adxl355_get_odr_string(adxl355_output_data_rate_t odr)
{
    switch (odr) {
        case ADXL355_ODR_4000:
            return "4000 Hz";
        case ADXL355_ODR_2000:
            return "2000 Hz";
        case ADXL355_ODR_1000:
            return "1000 Hz";
        case ADXL355_ODR_500:
            return "500 Hz";
        case ADXL355_ODR_250:
            return "250 Hz";
        case ADXL355_ODR_125:
            return "125 Hz";
        case ADXL355_ODR_62_5:
            return "62.5 Hz";
        case ADXL355_ODR_31_25:
            return "31.25 Hz";
        case ADXL355_ODR_15_625:
            return "15.625 Hz";
        case ADXL355_ODR_7_813:
            return "7.813 Hz";
        case ADXL355_ODR_3_906:
            return "3.906 Hz";
        default:
            return "Unknown";
    }
}

main.cpp

/**
 * @file main.c
 * @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
 * @brief 
 * @version 1.0
 * @date 2024-11-17
 * 
 * @copyright Copyright (c) 2024
 * 
 */

/* DEPENDENCIES */
// ESP
#include "esp_system.h" // ESP32 System
#include "nvs_flash.h"  // ESP32 NVS
#include "esp_chip_info.h" // ESP32 Chip Info
#include "esp_psram.h" // ESP32 PSRAM
#include "esp_flash.h" // ESP32 Flash
#include "esp_log.h" // ESP32 Logging

// BSP
#include "led.h"
#include "exit.h"
#include "spi.h"
#include "lcd.h"
#include "tim.h"
#include "esp_rtc.h"
#include "spi_sdcard.h"
#include "wifi_wpa2_enterprise.h"
#include "mqtt.h"
#include "adxl355.h"

/* Variables */
const char *TAG = "NEXNODE";

/* ADXL355 handle */
adxl355_handle_t adxl355_handle;

extern "C" void app_main();
/**
 * @brief Entry point of the program
 * @param None
 * @retval None
 */
void app_main()
{
    esp_err_t ret;
    uint32_t flash_size;
    esp_chip_info_t chip_info;

    char mqtt_pub_buff[64];
    // int count = 0;

    /* ADXL355 variables */
    adxl355_accelerations_t accel;
    // adxl355_raw_accelerations_t raw_accel;  // Unused variable, commented out
    float temperature;

    // SPI3 device handle is now managed internally by ADXL355

    // Initialize NVS
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase()); // Erase if needed
        ret = nvs_flash_init();
    }

    // Get FLASH size
    esp_flash_get_size(NULL, &flash_size);
    esp_chip_info(&chip_info);

    // Display CPU core count
    printf("CPU Cores: %d\n", chip_info.cores);

    // Display FLASH size
    printf("Flash size: %ld MB flash\n", flash_size / (1024 * 1024));

    // Display PSRAM size
    printf("PSRAM size: %d bytes\n", esp_psram_get_size());

    // BSP Initialization
    led_init();
    exit_init();
    spi2_init();
    // spi3_init();  // Refactored, now called during ADXL355 initialization
    lcd_init();

    // spiffs_test();                                                  /* Run SPIFFS test */
    while (sd_card_init())                               /* SD card not detected */
    {
        lcd_show_string(0, 0, 200, 16, 16, "SD Card Error!", RED);
        vTaskDelay(500);
        lcd_show_string(0, 20, 200, 16, 16, "Please Check!", RED);
        vTaskDelay(500);
    }

    // clean the screen
    lcd_clear(WHITE);

    lcd_show_string(0, 0, 200, 16, 16, "SD Initialized!", RED);

    sd_card_test_filesystem();                                        /* Run SD card test */

    lcd_show_string(0, 0, 200, 16, 16, "SD Tested CSW! ", RED);

    // sd_card_unmount();

    vTaskDelay(3000);

    // Initialize WiFi and MQTT first
    lcd_show_string(0, 0, lcd_self.width, 16, 16, "WiFi STA Test  ", RED);

    ret = wifi_sta_wpa2_init();
    if(ret == ESP_OK)
    {
        ESP_LOGI(TAG_WIFI, "WiFi STA Init OK");
        lcd_show_string(0, 0, lcd_self.width, 16, 16, "WiFi STA Test OK", RED);
    }
    else
    {
        ESP_LOGE(TAG_WIFI, "WiFi STA Init Failed");
    }

    // only when the ip is obtained, start mqtt
    EventBits_t ev = 0;
    ev = xEventGroupWaitBits(wifi_event_group,CONNECTED_BIT,pdTRUE,pdFALSE,portMAX_DELAY);
    if(ev & CONNECTED_BIT)
    {
        mqtt_app_start();
    }

    // Initialize and test ADXL355 after WiFi and MQTT
    lcd_clear(WHITE);
    lcd_show_string(0, 0, 200, 16, 16, "Testing ADXL355...", BLUE);

    ESP_LOGI(TAG, "=== MAIN: Starting ADXL355 test ===");

    // Step 1: Initialize SPI3 bus (general purpose)
    spi3_init();

    // Step 2: Initialize ADXL355 sensor (includes SPI device configuration)
    ret = adxl355_init(&adxl355_handle, ADXL355_RANGE_2G, ADXL355_ODR_1000);

    if (ret == ESP_OK) {
        ESP_LOGI(TAG, "✓ ADXL355 initialization successful!");
        lcd_show_string(0, 20, 200, 16, 16, "ADXL355 OK!", GREEN);

        // Read device info
        adxl355_device_info_t device_info;
        esp_err_t info_ret = adxl355_read_device_info(&adxl355_handle, &device_info);

        if (info_ret == ESP_OK) {
            ESP_LOGI(TAG, "Device ID: 0x%02X, Vendor ID: 0x%02X", device_info.device_id, device_info.vendor_id);

            char info_str[64];
            snprintf(info_str, sizeof(info_str), "ID: 0x%02X, 0x%02X, 0x%02X", 
                     device_info.vendor_id, device_info.family_id, device_info.device_id);
            lcd_show_string(0, 40, 200, 16, 16, info_str, BLACK);

            // Test temperature reading
            float test_temp;
            esp_err_t temp_test_ret = adxl355_read_temperature(&adxl355_handle, &test_temp);
            if (temp_test_ret == ESP_OK) {
                ESP_LOGI(TAG, "Temperature test: %.2f°C", test_temp);
                char temp_str[64];
                snprintf(temp_str, sizeof(temp_str), "Temp: %.1f°C", test_temp);
                lcd_show_string(0, 60, 200, 16, 16, temp_str, BLUE);
            } else {
                ESP_LOGE(TAG, "Temperature test failed: %s", esp_err_to_name(temp_test_ret));
                lcd_show_string(0, 60, 200, 16, 16, "Temp: Failed!", RED);
            }
        } else {
            ESP_LOGE(TAG, "✗ Failed to read device info: %s", esp_err_to_name(info_ret));
        }

    } else {
        ESP_LOGE(TAG, "✗ ADXL355 initialization failed: %s", esp_err_to_name(ret));
        lcd_show_string(0, 20, 200, 16, 16, "ADXL355 Failed!", RED);
    }

    vTaskDelay(3000);

    ESP_LOGI(TAG, "ADXL355 initialized and ready for MQTT publishing");

    ESP_LOGI(TAG, "=== MAIN: Entering main loop ===");

    while (1)
    {
        if(s_is_mqtt_connected)
        {
            // Read both acceleration and temperature data
            esp_err_t accel_ret = adxl355_read_accelerations(&adxl355_handle, &accel);
            esp_err_t temp_ret = adxl355_read_temperature(&adxl355_handle, &temperature);

            if (accel_ret == ESP_OK && temp_ret == ESP_OK) {
                // Format MQTT payload with both acceleration and temperature
                snprintf(mqtt_pub_buff, 64,
                    "{\"x\":%.4f,\"y\":%.4f,\"z\":%.4f,\"temp\":%.2f}",
                    accel.x, accel.y, accel.z, temperature);

                int mqtt_ret = esp_mqtt_client_publish(s_mqtt_client, MQTT_PUBLIC_TOPIC,
                                mqtt_pub_buff, strlen(mqtt_pub_buff), 1, 0);

                if (mqtt_ret < 0) {
                    ESP_LOGE(TAG, "✗ MQTT publish failed: %d", mqtt_ret);
                }

                // Output both acceleration and temperature to console
                printf("Accelerations: X: %f g, Y: %f g, Z: %f g | Temperature: %.2f°C\n", 
                       accel.x, accel.y, accel.z, temperature);

                // Also log to ESP log system
                ESP_LOGI(TAG, "Data: X=%.4f, Y=%.4f, Z=%.4f g, Temp=%.2f°C", 
                         accel.x, accel.y, accel.z, temperature);

            } else {
                if (accel_ret != ESP_OK) {
                    ESP_LOGE(TAG, "✗ Failed to read ADXL355 acceleration data: %s", esp_err_to_name(accel_ret));
                }
                if (temp_ret != ESP_OK) {
                    ESP_LOGE(TAG, "✗ Failed to read ADXL355 temperature data: %s", esp_err_to_name(temp_ret));
                }
            }
        } else {
            ESP_LOGW(TAG, "MQTT not connected, skipping data read");
        }

        led_toggle();
        vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
}

总结

1. 初始化与配置相关接口

函数原型 说明 用法示例
esp_err_t adxl355_init(adxl355_handle_t *handle, adxl355_range_t range, adxl355_output_data_rate_t odr) 初始化 ADXL355,包括 SPI 配置、量程和速率设置,自动复位设备 adxl355_init(&adxl355_handle, ADXL355_RANGE_2G, ADXL355_ODR_1000);
esp_err_t adxl355_deinit(adxl355_handle_t *handle) 反初始化,关闭测量,清理句柄 adxl355_deinit(&adxl355_handle);
esp_err_t adxl355_reset(adxl355_handle_t *handle) 复位设备,恢复出厂状态 adxl355_reset(&adxl355_handle);

Insight:初始化流程会自动完成 SPI 设备注册、寄存器配置和设备自检,建议在主程序启动阶段调用,确保硬件和驱动状态一致。


2. 设备信息与状态接口

函数原型 说明 用法示例
esp_err_t adxl355_read_device_info(adxl355_handle_t *handle, adxl355_device_info_t *device_info) 读取设备信息(Vendor/Device/Revision ID) adxl355_read_device_info(&adxl355_handle, &device_info);
esp_err_t adxl355_read_status(adxl355_handle_t *handle, adxl355_status_t *status) 读取状态寄存器(数据就绪、FIFO满等) adxl355_read_status(&adxl355_handle, &status);

Insight:设备信息可用于硬件兼容性检查,状态寄存器可用于中断或轮询方式的数据采集。


3. 数据采集接口

函数原型 说明 用法示例
esp_err_t adxl355_read_accelerations(adxl355_handle_t *handle, adxl355_accelerations_t *accel) 读取三轴加速度(单位:g,自动转换) adxl355_read_accelerations(&adxl355_handle, &accel);
esp_err_t adxl355_read_raw_accelerations(adxl355_handle_t *handle, adxl355_raw_accelerations_t *raw_accel) 读取原始三轴加速度(20位原始值) adxl355_read_raw_accelerations(&adxl355_handle, &raw_accel);
esp_err_t adxl355_read_temperature(adxl355_handle_t *handle, float *temp) 读取温度(单位:℃) adxl355_read_temperature(&adxl355_handle, &temperature);
esp_err_t adxl355_read_raw_temperature(adxl355_handle_t *handle, uint16_t *raw_temp) 读取原始温度数据 adxl355_read_raw_temperature(&adxl355_handle, &raw_temp);

Insight:推荐优先使用自动转换接口,便于直接用于物联网数据发布和显示。原始数据接口适合做底层调试或自定义算法。


4. 工作模式与功能使能接口

函数原型 说明 用法示例
esp_err_t adxl355_enable_measurement(adxl355_handle_t *handle) 使能测量模式 adxl355_enable_measurement(&adxl355_handle);
esp_err_t adxl355_disable_measurement(adxl355_handle_t *handle) 进入待机模式 adxl355_disable_measurement(&adxl355_handle);
esp_err_t adxl355_is_measurement_enabled(adxl355_handle_t *handle, bool *is_enabled) 查询测量模式状态 adxl355_is_measurement_enabled(&adxl355_handle, &is_enabled);
esp_err_t adxl355_enable_temperature(adxl355_handle_t *handle) 使能温度处理 adxl355_enable_temperature(&adxl355_handle);
esp_err_t adxl355_disable_temperature(adxl355_handle_t *handle) 关闭温度处理 adxl355_disable_temperature(&adxl355_handle);
esp_err_t adxl355_is_temperature_enabled(adxl355_handle_t *handle, bool *is_enabled) 查询温度处理状态 adxl355_is_temperature_enabled(&adxl355_handle, &is_enabled);
esp_err_t adxl355_enable_data_ready(adxl355_handle_t *handle) 使能数据就绪输出 adxl355_enable_data_ready(&adxl355_handle);
esp_err_t adxl355_disable_data_ready(adxl355_handle_t *handle) 关闭数据就绪输出 adxl355_disable_data_ready(&adxl355_handle);
esp_err_t adxl355_is_data_ready_enabled(adxl355_handle_t *handle, bool *is_enabled) 查询数据就绪输出状态 adxl355_is_data_ready_enabled(&adxl355_handle, &is_enabled);

Insight:通过灵活切换测量/待机、温度和数据就绪输出,可实现低功耗和高实时性应用,适合嵌入式和远程采集场景。


5. 参数设置与查询接口

函数原型 说明 用法示例
esp_err_t adxl355_set_range(adxl355_handle_t *handle, adxl355_range_t range) 设置加速度计量程 adxl355_set_range(&adxl355_handle, ADXL355_RANGE_4G);
esp_err_t adxl355_read_range(adxl355_handle_t *handle, adxl355_range_t *range) 查询当前量程 adxl355_read_range(&adxl355_handle, &range);
esp_err_t adxl355_set_odr(adxl355_handle_t *handle, adxl355_output_data_rate_t odr) 设置输出数据速率 adxl355_set_odr(&adxl355_handle, ADXL355_ODR_500);
esp_err_t adxl355_read_odr(adxl355_handle_t *handle, adxl355_output_data_rate_t *odr) 查询当前输出速率 adxl355_read_odr(&adxl355_handle, &odr);
esp_err_t adxl355_read_acceleration_scale_factor(adxl355_handle_t *handle, float *scale_factor) 查询当前量程对应的比例因子 adxl355_read_acceleration_scale_factor(&adxl355_handle, &scale_factor);

Insight:量程和速率可动态调整,适合不同精度和带宽需求。比例因子接口方便自定义物理量转换。


6. 底层寄存器与SPI接口(高级/调试)

函数原型 说明 用法示例
esp_err_t adxl355_spi_read(adxl355_handle_t *handle, uint8_t cmd, uint8_t *data, int len) SPI 读命令,底层寄存器批量读取 adxl355_spi_read(&adxl355_handle, cmd, data, len);
esp_err_t adxl355_read_register(adxl355_handle_t *handle, uint8_t reg, uint8_t *value) 读单寄存器 adxl355_read_register(&adxl355_handle, reg, &value);
esp_err_t adxl355_write_register(adxl355_handle_t *handle, uint8_t reg, uint8_t value) 写单寄存器 adxl355_write_register(&adxl355_handle, reg, value);

Insight:底层接口适合做寄存器级调试、兼容性适配和自定义扩展,普通应用建议用高层接口。


实践建议

  • 初始化建议:建议在主程序启动时完成初始化和自检,确保硬件和驱动状态一致。
  • 数据采集:推荐用高层自动转换接口,便于直接用于物联网发布和显示。
  • 低功耗管理:合理切换测量/待机模式,关闭不必要的功能(如温度),可显著降低功耗。
  • 调试扩展:底层寄存器接口可用于高级调试和功能扩展,适合开发和测试阶段。