diff --git a/boards/shields/adi_eval_ade9153ashieldz/Kconfig.shield b/boards/shields/adi_eval_ade9153ashieldz/Kconfig.shield new file mode 100644 index 0000000000000..5767e1bcec334 --- /dev/null +++ b/boards/shields/adi_eval_ade9153ashieldz/Kconfig.shield @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Plentify (Pty) Ltd +# SPDX-License-Identifier: Apache-2.0 + +config SHIELD_ADI_EVAL_ADE9153ASHIELDZ + def_bool $(shields_list_contains,adi_eval_ade9153ashieldz) + select ADE9153A + select SENSOR + select GPIO diff --git a/boards/shields/adi_eval_ade9153ashieldz/adi_eval_ade9153ashieldz.overlay b/boards/shields/adi_eval_ade9153ashieldz/adi_eval_ade9153ashieldz.overlay new file mode 100644 index 0000000000000..b5e4d5da799c3 --- /dev/null +++ b/boards/shields/adi_eval_ade9153ashieldz/adi_eval_ade9153ashieldz.overlay @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Plentify (Pty) Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + +}; + +&arduino_spi { + status = "okay"; + + cs-gpios = <&arduino_header 14 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; /* D8 */ + + ade9153a@0 { + compatible = "adi,ade9153a"; + status = "okay"; + reg = <0>; + spi-max-frequency = <1000000>; /* conservatively set to 1MHz */ + cf-gpios = <&arduino_header 8 (GPIO_ACTIVE_HIGH)>; + irq-gpios = <&arduino_header 9 (GPIO_ACTIVE_LOW)>; + reset-gpios = <&arduino_header 10 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + }; +}; diff --git a/boards/shields/adi_eval_ade9153ashieldz/doc/EV-ADE9153ASHIELDZANGLE-web.png b/boards/shields/adi_eval_ade9153ashieldz/doc/EV-ADE9153ASHIELDZANGLE-web.png new file mode 100644 index 0000000000000..8ebefd8154e9c Binary files /dev/null and b/boards/shields/adi_eval_ade9153ashieldz/doc/EV-ADE9153ASHIELDZANGLE-web.png differ diff --git a/boards/shields/adi_eval_ade9153ashieldz/doc/index.rst b/boards/shields/adi_eval_ade9153ashieldz/doc/index.rst new file mode 100644 index 0000000000000..833b00fa8d34a --- /dev/null +++ b/boards/shields/adi_eval_ade9153ashieldz/doc/index.rst @@ -0,0 +1,77 @@ +.. _adafruit_data_logger_shield: + +Adafruit Data Logger Shield +########################### + +Overview +******** + +The `Adafruit Data Logger Shield`_ rev. B features an `NXP PCF8523 Real-Time Clock/Calendar with +Battery Backup`_, an SD card interface, two user LEDs, and a prototyping area. + +.. figure:: adafruit_data_logger.jpg + :align: center + :alt: Adafruit Data Logger Shield + + Adafruit Data Logger Shield (Credit: Adafruit) + +.. note:: + The older revision A of the Adafruit Data Logger Shield is not supported. + +Pin Assignments +=============== + ++-----------------------+---------------------------------------------+ +| Shield Connector Pin | Function | ++=======================+=============================================+ +| D3 | User LED1 (green) [1]_ | ++-----------------------+---------------------------------------------+ +| D4 | User LED2 (red) [1]_ | ++-----------------------+---------------------------------------------+ +| D7 | PCF8523 RTC INT1 [2]_ | ++-----------------------+---------------------------------------------+ +| D10 | SD card SPI CS | ++-----------------------+---------------------------------------------+ +| D11 | SD card SPI MOSI | ++-----------------------+---------------------------------------------+ +| D12 | SD card SPI MISO | ++-----------------------+---------------------------------------------+ +| D13 | SD card SPI SCK | ++-----------------------+---------------------------------------------+ +| SDA | PCF8523 RTC I2C SDA | ++-----------------------+---------------------------------------------+ +| SCL | PCF8523 RTC I2C SCL | ++-----------------------+---------------------------------------------+ + +.. [1] The user LEDs are not connected to ``D3`` and ``D4`` by default. Jumper or jumper wire + connections must be established between the ``L1`` and ``Digital I/O 3`` pins for ``LED1`` + and ``L2`` and ``Digital I/O 4`` pins for ``LED2`` if they are to be used. + +.. [2] The PCF8523 RTC ``INT1`` interrupt output pin is not connected to ``D7`` by default. A jumper + wire connection must be established between the ``SQ`` pin and the ``Digital I/O 7`` pin in + order to use the RTC interrupt functionality (i.e. alarm callback, 1 pulse per second + callback). The ``INT1`` interrupt output is open-drain, but the shield definition enables an + internal GPIO pull-up and thus no external pull-up resistor is needed. + +Requirements +************ + +This shield can only be used with a board which provides a configuration for Arduino connectors and +defines node aliases for SPI and GPIO interfaces (see :ref:`shields` for more details). + +Programming +*********** + +Set ``-DSHIELD=adafruit_data_logger`` when you invoke ``west build``. For example: + +.. zephyr-app-commands:: + :zephyr-app: tests/drivers/rtc/rtc_api + :board: frdm_k64f + :shield: adafruit_data_logger + :goals: build + +.. _Adafruit Data Logger Shield: + https://learn.adafruit.com/adafruit-data-logger-shield/ + +.. _NXP PCF8523 Real-Time Clock/Calendar with Battery Backup: + https://www.nxp.com/docs/en/data-sheet/PCF8523.pdf diff --git a/drivers/sensor/adi/CMakeLists.txt b/drivers/sensor/adi/CMakeLists.txt index cf2e79c2db61e..7aff123c6250a 100644 --- a/drivers/sensor/adi/CMakeLists.txt +++ b/drivers/sensor/adi/CMakeLists.txt @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # zephyr-keep-sorted-start +add_subdirectory_ifdef(CONFIG_ADE9153A ade9153a) add_subdirectory_ifdef(CONFIG_ADLTC2990 adltc2990) add_subdirectory_ifdef(CONFIG_ADT7310 adt7310) add_subdirectory_ifdef(CONFIG_ADT7420 adt7420) diff --git a/drivers/sensor/adi/Kconfig b/drivers/sensor/adi/Kconfig index 76305d1fd2720..87311215bd91b 100644 --- a/drivers/sensor/adi/Kconfig +++ b/drivers/sensor/adi/Kconfig @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # zephyr-keep-sorted-start +source "drivers/sensor/adi/ade9153a/Kconfig" source "drivers/sensor/adi/adltc2990/Kconfig" source "drivers/sensor/adi/adt7310/Kconfig" source "drivers/sensor/adi/adt7420/Kconfig" diff --git a/drivers/sensor/adi/ade9153a/CMakeLists.txt b/drivers/sensor/adi/ade9153a/CMakeLists.txt new file mode 100644 index 0000000000000..223f399bd4970 --- /dev/null +++ b/drivers/sensor/adi/ade9153a/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Plentify (Pty) Ltd +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(ade9153a.c) +zephyr_library_sources_ifdef(CONFIG_ADE9153A_TRIGGER ade9153a_trigger.c) diff --git a/drivers/sensor/adi/ade9153a/Kconfig b/drivers/sensor/adi/ade9153a/Kconfig new file mode 100644 index 0000000000000..5176ac95f12a9 --- /dev/null +++ b/drivers/sensor/adi/ade9153a/Kconfig @@ -0,0 +1,221 @@ +# +# ADE9153a Energy metering IC configuration options +# +# Copyright (c) 2025 Plentify (Pty) Ltd. +# SPDX-License-Identifier: Apache-2.0 + +menuconfig ADE9153A + bool "ADE9153A Energy metering IC with autocalibration" + default y + depends on DT_HAS_ADI_ADE9153A_ENABLED + select SPI + help + Enable driver for ADE9153A Energy metering IC. + +if ADE9153A + +config ADE9153A_RESET_ACTIVE_TIME + int "The amount of time (ms) spent between toggling the reset pin on startup" + default 100 + help + The time the reset pin will be holding down during reset. + +config ADE9153A_POST_RESET_DELAY + int "The amount of time (ms) spent after reset on startup" + default 1000 + help + The delay to wait after resetting the ADE9153A + +config ADE9153A_AUTO_CALIBRATE + bool "Auto calibrate" + default y + help + After initialized, the sensor driver will start the autocalibration procedure. + +config ADE9153A_AI_PGAGAIN + hex "Current channel A PGAGAIN value" + default "0x000A" + help + Signal on IAN, current channel gain + +config ADE9153A_BI_CURRENT_CHANNEL + bool "Use B current channel" + +config ADE9153A_BI_PGAGAIN + hex "Current channel B PGAGAIN value" + default "0x0000" + depends on ADE9153A_BI_CURRENT_CHANNEL + help + Signal on BI, current channel gain + +config ADE9153A_CONFIG0 + hex "CONFIG0 settings" + default "0x00000000" + +config ADE9153A_CONFIG1 + hex "CONFIG1 settings" + default "0x0300" + +config ADE9153A_CONFIG2 + hex "CONFIG2 settings" + default "0x0C00" + +config ADE9153A_CONFIG3 + hex "CONFIG3 settings" + default "0x0000" + +config ADE9153A_ACCMODE + hex "Energy accumulation modes, Bit 4, 0 for 50Hz, 1 for 60Hz" + default "0x0010" + +config ADE9153A_VLEVEL + hex "Assuming Vnom=1/2 of fullscale" + default "0x002C11E8" + +config ADE9153A_ZX_CFG + hex "ZX low-pass filter select" + default "0x0000" + +config ADE9153A_MASK + hex "Interrupt mask" + default "0x00000100" + +config ADE9153A_ACT_NL_LVL + hex "No load threshold for Active power" + default "0x000033C8" + +config ADE9153A_REACT_NL_LVL + hex "No load threshold for Reactive power" + default "0x000033C8" + +config ADE9153A_APP_NL_LVL + hex "No load threshold for Apparent power" + default "0x000033C8" + +config ADE9153A_RUN_ON + hex "DSP on" + default "0x0001" + + +config ADE9153A_COMPMODE + hex "Computation mode register" + default "0x0005" + +config ADE9153A_VDIV_RSMALL + hex "Small resistor on board is 1kOhm=0x3E8" + default "0x03E8" + +config ADE9153A_EP_CFG + hex "Energy accumulation configuration" + default "0x0009" + +config ADE9153A_EGY_TIME + hex "Accumulate energy for 4000 samples" + default "0x0F9F" + +config ADE9153A_TEMP_CFG + hex "Temperature sensor configuration" + default "0x000C" + + +config ADE9153A_SETUP_ON_STARTUP + bool "Run the setup during startup" + default y + +config ADE9153A_ACAL_ON_STARTUP + bool "Run autocalibration on startup" + default y + +# FIXME: [rodrigopex] Take a look at the comment values. +# Ideal Calibration Values for EV-ADE9153ASHIELDZ Shield Based on Sensor Values +# shield value 0.838190 = Q31(value=7031246, shift=0) +config ADE9153A_CAL_IRMS_CC + int "RMS current Calibration Constant (CC) Q31 value" + default 1799999318 + help + The original default value is 0.838190. The Kconfig value is: (0.838190 * 0x7FFFFF)/(1U << shift)) + . In the driver the value + will be recovered as follows: (1799999318 /(double) 0x7FFFFFFF)) * (1U << shift) = + 0.8381899999. + +config ADE9153A_CAL_IRMS_CC_SHIFT + int "RMS current Calibration Constant (CC) Q31 shift value" + default 0 + +config ADE9153A_CAL_VRMS_CC # shield value 13.41105 = Q31(value=7031251, shift=4) + int "RMS voltage Calibration Constant (CC) Q31 value" + default 1800000660 + help + Same happens as CAL_IRMS_CC. +# FIXME: [rodrigopex] Move the complete documentation to the first Q31 value in the Kconfig to help developers +config ADE9153A_CAL_VRMS_CC_SHIFT + int "RMS voltage Calibration Constant (CC) Q31 shift value" + default 4 + help + Number used to scale the number. ADE9153A_CAL_VRMS_CC/(1U << ADE9153A_CAL_VRMS_CC_SHIFT) + to keep the number inside the interval [-1.0, 1.0[. The default original value is + 13.41105. To make that normalized we need to use a shift of 7. The value is (13.41105/1U + << 7) = 0.104773827877256. + + include + include + + int main(int argc, char *argv[]) { + double original_value = 13.41105; + uint8_t shift = 7; + int64_t q_value = (original_value / (1U << shift)) * INT32_MAX; // 0x7FFFFF + double recovered_value = ((double)(q_value) / INT32_MAX) * (1U << shift); + printf("The result is %0.20lf\n", recovered_value); + return 0; + } + +config ADE9153A_CAL_POWER_CC # shield value 1508.743 = Q31(value=6179810, shift=11) + int "Power Calibration Constant (CC) Q31 value" + default 1582031699 + +config ADE9153A_CAL_POWER_CC_SHIFT + int "Power Calibration Constant (CC) Q31 shift value" + default 11 + +config ADE9153A_CAL_ENERGY_CC # shield value 0.858307 = Q31(value=7200000, shift=0) + int "Energy Calibration Constant (CC) Q31 value" + default 1843200246 + +config ADE9153A_CAL_ENERGY_CC_SHIFT + int "Energy Calibration Constant (CC) Q31 shift value" + default 0 + +config ADE9153A_AI_TURBO_CAL_TIME + int "The time spent on the current turbo calibration in milliseconds" + default 2000 + +config ADE9153A_AV_TURBO_CAL_TIME + int "The time spent on the voltage turbo calibration in milliseconds" + default 4000 + +config ADE9153A_TRIGGER + bool "Trigger on" + +if ADE9153A_TRIGGER + +config ADE9153A_THREAD_STACK_SIZE + int "Trigger thread stack size." + default 1024 + +config ADE9153A_THREAD_PRIORITY + int "Trigger thread priority." + default 4 + +config ADE9153A_TRIGGER_IRQ + bool "Trigger on IRQ" + default y + depends on GPIO + +config ADE9153A_TRIGGER_CF + bool "Trigger on CF" + default y + depends on GPIO + +endif # ADE9153A_TRIGGER + +endif # ADE9153A diff --git a/drivers/sensor/adi/ade9153a/ade9153a.c b/drivers/sensor/adi/ade9153a/ade9153a.c new file mode 100644 index 0000000000000..8ced6a77a788c --- /dev/null +++ b/drivers/sensor/adi/ade9153a/ade9153a.c @@ -0,0 +1,602 @@ +/* + * Copyright (c) 2025 Plentify (Pty) Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT adi_ade9153a + +#include +#include +#include +#include +#include + +#include "ade9153a.h" + +#include +LOG_MODULE_REGISTER(ade9153a, CONFIG_SENSOR_LOG_LEVEL); + +/* Register operations. + * The bit[3] = 0 indicates a write action and[3] = 1 indicates a read + */ +#define ADE9153A_READ_REG BIT(3) +#define ADE9153A_WRITE_REG (0) +#define RETURN_ON_ERR(_err) \ + if (_err != 0) { \ + LOG_ERR("Error: %d", _err); \ + return _err; \ + } + +static int ade9153a_reg_access(const struct device *dev, uint8_t cmd, uint16_t reg_addr, void *data, + size_t length) +{ + const struct ade9153a_config *cfg = dev->config; + + /* The bit [3] = 0 indicates a write action and [3] = 1 indicates a read. + * When reading the cmd value is 8 == BIT(3), when writing cmd is 0 + */ + uint16_t access = (((uint16_t)(reg_addr << 4U) & 0xFFF0U) + cmd); + + /* Swap Endianness to Transmit MSB first as per ADE9153A requirements */ + access = (uint16_t)((access & 0x00FF) << 8) | ((access & 0xFF00) >> 8); + + const struct spi_buf buf[2] = {{.buf = &access, .len = sizeof(access)}, + {.buf = data, .len = length}}; + struct spi_buf_set tx = { + .buffers = buf, + }; + + if (cmd == ADE9153A_READ_REG) { + const struct spi_buf_set rx = {.buffers = buf, .count = 2}; + + tx.count = 1; + + return spi_transceive_dt(&cfg->spi_dt_spec, &tx, &rx); + } + + tx.count = 2; + + return spi_write_dt(&cfg->spi_dt_spec, &tx); +} + +static inline int ade9153a_get_reg16(const struct device *dev, uint16_t reg_addr, uint16_t *data) +{ + uint8_t data_raw[2] = {0}; + + int err = ade9153a_reg_access(dev, ADE9153A_READ_REG, reg_addr, data_raw, 2U); + + if (err) { + return err; + } + + uint16_t data_msb = (((uint16_t)data_raw[0]) << 8U) + (uint16_t)data_raw[1]; + + *data = data_msb; + + return 0; +} + +static inline int ade9153a_get_reg32(const struct device *dev, uint16_t reg_addr, uint32_t *data) +{ + uint8_t data_raw[4] = {0}; + + int err = ade9153a_reg_access(dev, ADE9153A_READ_REG, reg_addr, data_raw, 4U); + + if (err) { + return err; + } + + uint32_t data_msb = (((uint32_t)data_raw[0]) << 24U) + (((uint32_t)data_raw[1]) << 16U) + + (((uint32_t)data_raw[2]) << 8U) + (uint32_t)(data_raw[3]); + + *data = data_msb; + + return 0; +} +static inline int ade9153a_set_reg16(const struct device *dev, uint16_t reg_addr, uint16_t data) +{ + uint8_t data_raw[2] = {(uint8_t)(data >> 8U), (uint8_t)data}; + return ade9153a_reg_access(dev, ADE9153A_WRITE_REG, reg_addr, data_raw, 2U); +} + +static inline int ade9153a_set_reg32(const struct device *dev, uint16_t reg_addr, uint32_t data) +{ + uint8_t data_raw[4] = {(uint8_t)(data >> 24U), (uint8_t)(data >> 16U), + (uint8_t)(data >> 8U), (uint8_t)data}; + + return ade9153a_reg_access(dev, ADE9153A_WRITE_REG, reg_addr, data_raw, 4U); +} + +int start_acal(const struct device *dev, uint32_t reg_ms_acal_cfg) +{ + int attempts = 0; + uint32_t ms_status_current_reg; + + ade9153a_get_reg32(dev, ADE9153A_REG_MS_STATUS_CURRENT, &ms_status_current_reg); + + while ((ms_status_current_reg & 0x00000001) == 0) { + ade9153a_get_reg32(dev, ADE9153A_REG_MS_STATUS_CURRENT, &ms_status_current_reg); + if (attempts > 15) { + return -EBUSY; + } + attempts++; + k_msleep(100); + } + + ade9153a_set_reg32(dev, ADE9153A_REG_MS_ACAL_CFG, reg_ms_acal_cfg); /* turbo */ + + return 0; +} + +void stop_acal(const struct device *dev) +{ + ade9153a_set_reg32(dev, ADE9153A_REG_MS_ACAL_CFG, 0x00000000); +} + +void apply_acal(const struct device *dev, double AICC, double AVCC) +{ + int err; + int32_t AIGAIN; + int32_t AVGAIN; + + AIGAIN = (AICC / (CAL_IRMS_CC * 1000) - 1) * 134217728; + AVGAIN = (AVCC / (CAL_VRMS_CC * 1000) - 1) * 134217728; + + LOG_DBG("AIGAIN: %d", AIGAIN); + err = ade9153a_set_reg32(dev, ADE9153A_REG_AIGAIN, AIGAIN); + __ASSERT(err == 0, "Could not set AIGAIN register. Err=%d", AIGAIN); + + LOG_DBG("AVGAIN: %d", AVGAIN); + err = ade9153a_set_reg32(dev, ADE9153A_REG_AVGAIN, AVGAIN); + __ASSERT(err == 0, "Could not set AVGAIN register. Err=%d", AIGAIN); +} + +int __weak ade9153a_start_autocalibration(const struct device *dev) +{ + LOG_DBG("Autocalibrating Current Channel:"); + start_acal(dev, 0x00000017); /* AITurbo */ + + uint32_t cert; + + for (int i = 0, iterations = 10; i < iterations; ++i) { + ade9153a_get_reg32(dev, ADE9153A_REG_MS_ACAL_AICERT, &cert); + LOG_DBG("[%d/%d] AICERT: %d.%d %%", i, iterations, cert / 10000, cert % 10000); + k_msleep(CONFIG_ADE9153A_AI_TURBO_CAL_TIME / iterations); + } + + stop_acal(dev); + + LOG_DBG("Autocalibrating Voltage Channel:"); + start_acal(dev, 0x00000043); /* AVTurbo */ + + for (int i = 0, iterations = 100; i < iterations; ++i) { + ade9153a_get_reg32(dev, ADE9153A_REG_MS_ACAL_AVCERT, &cert); + LOG_DBG("[%d/%d] AVCERT: %d.%d %%", i, iterations, cert / 10000, cert % 10000); + k_msleep(CONFIG_ADE9153A_AV_TURBO_CAL_TIME / iterations); + } + + stop_acal(dev); + + double aicc, avcc; + uint32_t temp_reg; + + ade9153a_get_reg32(dev, ADE9153A_REG_MS_ACAL_AICC, &temp_reg); /* turbo */ + + aicc = temp_reg / (double)2048; + + ade9153a_get_reg32(dev, ADE9153A_REG_MS_ACAL_AVCC, &temp_reg); /* turbo */ + + avcc = temp_reg / (double)2048; + + apply_acal(dev, aicc, avcc); + + LOG_DBG("Autocalibration...[ok]"); + k_msleep(100); + + return 0; +} + +static int ade9153a_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct ade9153a_data *data = dev->data; + double double_value; + + switch (chan) { + case SENSOR_CHAN_DIE_TEMP: { + uint16_t gain; + uint16_t offset; + + gain = (data->temperature_trim & 0xFFFF); /* Extract 16 LSB */ + offset = ((data->temperature_trim >> 16) & 0xFFFF); /* Extract 16 MSB */ + double_value = ((double)offset / 32.00) - + ((double)data->temperature_reg * (double)gain / (double)131072); + break; + } + case SENSOR_CHAN_AC_ACTIVE_ENERGY: { + /* Energy in mWhr */ + double_value = (double)data->active_energy_reg * CAL_ENERGY_CC / 1000; + + break; + } + case SENSOR_CHAN_AC_FUNDAMENTAL_REACTIVE_ENERGY: { + double_value = (double)data->fund_reactive_energy_reg * CAL_ENERGY_CC / 1000; + break; + } + case SENSOR_CHAN_AC_APPARENT_ENERGY: { + double_value = (double)data->apparent_energy_reg * CAL_ENERGY_CC / 1000; + break; + } + case SENSOR_CHAN_AC_ACTIVE_POWER: { + double_value = (double)data->active_power_reg * CAL_POWER_CC / 1000; + break; + } + case SENSOR_CHAN_AC_FUNDAMENTAL_REACTIVE_POWER: { + double_value = (double)data->fund_reactive_power_reg * CAL_POWER_CC / 1000; + break; + } + case SENSOR_CHAN_AC_APPARENT_POWER: { + double_value = (double)data->apparent_power_reg * CAL_POWER_CC / 1000; + break; + } + case SENSOR_CHAN_AC_CURRENT_RMS: { + /* RMS in mA */ + double_value = (double)data->current_rms_reg * CAL_IRMS_CC / 1000; + break; + } + case SENSOR_CHAN_AC_HALF_CURRENT_RMS: { + /* RMS in mV */ + double_value = (double)data->half_current_rms_reg * CAL_IRMS_CC / 1000; + break; + } + case SENSOR_CHAN_AC_VOLTAGE_RMS: { + /* Half-RMS in mV */ + double_value = (double)data->voltage_rms_reg * CAL_VRMS_CC / 1000; + break; + } + case SENSOR_CHAN_AC_HALF_VOLTAGE_RMS: { + /* Half-RMS in mV */ + double_value = (double)data->half_voltage_rms_reg * CAL_VRMS_CC / 1000; + break; + } + case SENSOR_CHAN_AC_POWER_FACTOR: { + /* Calculate PF */ + double_value = (double)data->power_factor_reg / (double)134217728; + break; + } + case SENSOR_CHAN_AC_PERIOD: { + /* Calculate Line Period */ + double_value = (double)(data->period_reg + 1) / (double)(4000 * 65536); + break; + } + case SENSOR_CHAN_AC_FREQUENCY: { + /* Calculate Frequency in Hz*/ + double_value = 100 * ((4000 * 65536) / (double)(data->period_reg + 1)); + break; + } + case SENSOR_CHAN_AC_ANGLE: { + double mulConstant; + + if ((data->acc_mode_reg & 0x0010) > 0) { + mulConstant = 0.02109375; /* multiplier constant for 60Hz system */ + } else { + mulConstant = 0.017578125; /* multiplier constant for 50Hz system */ + } + + /* Calculate Angle in degrees */ + double_value = data->angle_reg_av_ai_reg * mulConstant; + break; + } + default: + return -ENOTSUP; + } + + sensor_value_from_double(val, double_value); + + return 0; +} + +static int ade9153a_attr_set(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, const struct sensor_value *val) +{ + __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL); + + uint32_t attribute = attr; + + switch (attribute) { + case SENSOR_ATTR_ADE9153A_REGISTER: { + union ade9153a_register ade = {.as_sensor_value = *val}; + + if (ade.size == sizeof(uint16_t)) { + LOG_DBG("Data to write %X", (uint16_t)ade.value); + ade9153a_set_reg16(dev, ade.addr, (uint16_t)ade.value); + } else { + LOG_DBG("Data to write %X", ade.value); + ade9153a_set_reg32(dev, ade.addr, ade.value); + } + break; + } + case SENSOR_ATTR_ADE9153A_START_AUTOCALIBRATION: + ade9153a_start_autocalibration(dev); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ade9153a_attr_get(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, struct sensor_value *val) +{ + __ASSERT_NO_MSG(chan == SENSOR_CHAN_ALL); + __ASSERT_NO_MSG(((int)attr) == ((int)SENSOR_ATTR_ADE9153A_REGISTER)); + + union ade9153a_register ade = {.as_sensor_value = *val}; + + if (ade.size == sizeof(uint16_t)) { + uint16_t u16_value; + + ade9153a_get_reg16(dev, ade.addr, &u16_value); + + val->val1 = u16_value; + } else if (ade.size == sizeof(uint32_t)) { + ade9153a_get_reg32(dev, ade.addr, &(val->val1)); + } else { + return -EINVAL; + } + + return 0; +} + +static int ade9153a_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct ade9153a_data *data = dev->data; + + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_STATUS, (void *)&data->status_reg)); + + switch (chan) { + case SENSOR_CHAN_ALL: + case SENSOR_CHAN_DIE_TEMP: { + /* Starting temperature reading */ + RETURN_ON_ERR( + ade9153a_set_reg16(dev, ADE9153A_REG_TEMP_CFG, CONFIG_ADE9153A_TEMP_CFG)); + k_msleep(10); + + RETURN_ON_ERR( + ade9153a_get_reg32(dev, ADE9153A_REG_TEMP_TRIM, &data->temperature_trim)); + + /* SENSOR_CHAN_DIE_TEMP */ + RETURN_ON_ERR( + ade9153a_get_reg16(dev, ADE9153A_REG_TEMP_RSLT, &data->temperature_reg)); + + if (chan != SENSOR_CHAN_ALL) { + break; + } + } + case SENSOR_CHAN_AC_ACTIVE_ENERGY: + case SENSOR_CHAN_AC_FUNDAMENTAL_REACTIVE_ENERGY: + case SENSOR_CHAN_AC_APPARENT_ENERGY: + /* SENSOR_CHAN_AC_ACTIVE_ENERGY */ + RETURN_ON_ERR( + ade9153a_get_reg32(dev, ADE9153A_REG_AWATTHR_HI, &data->active_energy_reg)); + + /* SENSOR_CHAN_AC_FUNDAMENTAL_REACTIVE_ENERGY */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AFVARHR_HI, + &data->fund_reactive_energy_reg)); + + /* SENSOR_CHAN_AC_APPARENT_ENERGY */ + RETURN_ON_ERR( + ade9153a_get_reg32(dev, ADE9153A_REG_AVAHR_HI, &data->apparent_energy_reg)); + + if (chan != SENSOR_CHAN_ALL) { + break; + } + case SENSOR_CHAN_AC_ACTIVE_POWER: + case SENSOR_CHAN_AC_FUNDAMENTAL_REACTIVE_POWER: + case SENSOR_CHAN_AC_APPARENT_POWER: + /* SENSOR_CHAN_AC_ACTIVE_POWER */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AWATT, &data->active_power_reg)); + + /* SENSOR_CHAN_AC_FUNDAMENTAL_REACTIVE_POWER */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AFVAR, + &data->fund_reactive_power_reg)); + + /* SENSOR_CHAN_AC_APPARENT_POWER */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AVA, &data->apparent_power_reg)); + + if (chan != SENSOR_CHAN_ALL) { + break; + } + case SENSOR_CHAN_AC_CURRENT_RMS: + case SENSOR_CHAN_AC_VOLTAGE_RMS: + /* SENSOR_CHAN_AC_CURRENT_RMS */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AIRMS, &data->current_rms_reg)); + + /* SENSOR_CHAN_AC_VOLTAGE_RMS */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AVRMS, &data->voltage_rms_reg)); + + if (chan != SENSOR_CHAN_ALL) { + break; + } + case SENSOR_CHAN_AC_HALF_VOLTAGE_RMS: + case SENSOR_CHAN_AC_HALF_CURRENT_RMS: + /* SENSOR_CHAN_AC_HALF_CURRENT_RMS */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AIRMS_OC, + &data->half_current_rms_reg)); + /* SENSOR_CHAN_AC_HALF_VOLTAGE_RMS */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_AVRMS_OC, + &data->half_voltage_rms_reg)); + + if (chan != SENSOR_CHAN_ALL) { + break; + } + case SENSOR_CHAN_AC_POWER_FACTOR: + case SENSOR_CHAN_AC_PERIOD: + case SENSOR_CHAN_AC_FREQUENCY: + case SENSOR_CHAN_AC_ANGLE: + /* SENSOR_CHAN_AC_POWER_FACTOR */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_APF, &data->power_factor_reg)); + RETURN_ON_ERR(ade9153a_get_reg16(dev, ADE9153A_REG_ACCMODE, &data->acc_mode_reg)); + + /* SENSOR_CHAN_AC_PERIOD */ + /* SENSOR_CHAN_AC_FREQUENCY */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_APERIOD, &data->period_reg)); + + /* SENSOR_CHAN_AC_ANGLE */ + RETURN_ON_ERR(ade9153a_get_reg32(dev, ADE9153A_REG_ANGL_AV_AI, + &data->angle_reg_av_ai_reg)); + + break; + + default: + return -ENOTSUP; + } + return 0; +} + +static const struct sensor_driver_api ade9153a_driver_api = { + .attr_set = ade9153a_attr_set, + .attr_get = ade9153a_attr_get, + .sample_fetch = ade9153a_sample_fetch, + .channel_get = ade9153a_channel_get, + IF_ENABLED(CONFIG_ADE9153A_TRIGGER, (.trigger_set = ade9153a_trigger_set))}; + +static void ade9153a_reset(const struct device *dev) +{ + const struct ade9153a_config *cfg = dev->config; + + gpio_pin_set_dt(&cfg->reset_gpio_dt_spec, 1); + + k_msleep(CONFIG_ADE9153A_RESET_ACTIVE_TIME); + + gpio_pin_set_dt(&cfg->reset_gpio_dt_spec, 0); + + k_msleep(CONFIG_ADE9153A_POST_RESET_DELAY); + + LOG_DBG("Reset Done"); +} + +static int ade9153a_probe(const struct device *dev) +{ + ade9153a_set_reg16(dev, ADE9153A_REG_RUN, CONFIG_ADE9153A_RUN_ON); + + k_msleep(100); + + uint32_t reg_value = 0; + + ade9153a_get_reg32(dev, ADE9153A_REG_VERSION_PRODUCT, ®_value); + + if (reg_value != 0x9153a) { + return -ENODEV; + } + + LOG_DBG("Communication attempt...[ok]"); + return 0; +} + +int __weak ade9153a_setup(const struct device *dev) +{ + ade9153a_set_reg16(dev, ADE9153A_REG_AI_PGAGAIN, CONFIG_ADE9153A_AI_PGAGAIN); + + ade9153a_set_reg32(dev, ADE9153A_REG_CONFIG0, CONFIG_ADE9153A_CONFIG0); + + ade9153a_set_reg16(dev, ADE9153A_REG_CONFIG1, CONFIG_ADE9153A_CONFIG1); + + ade9153a_set_reg16(dev, ADE9153A_REG_CONFIG2, CONFIG_ADE9153A_CONFIG2); + + ade9153a_set_reg16(dev, ADE9153A_REG_CONFIG3, CONFIG_ADE9153A_CONFIG3); + + ade9153a_set_reg16(dev, ADE9153A_REG_ACCMODE, CONFIG_ADE9153A_ACCMODE); + + ade9153a_set_reg32(dev, ADE9153A_REG_VLEVEL, CONFIG_ADE9153A_VLEVEL); + + ade9153a_set_reg16(dev, ADE9153A_REG_ZX_CFG, CONFIG_ADE9153A_ZX_CFG); + + ade9153a_set_reg32(dev, ADE9153A_REG_MASK, CONFIG_ADE9153A_MASK); + + ade9153a_set_reg32(dev, ADE9153A_REG_ACT_NL_LVL, CONFIG_ADE9153A_ACT_NL_LVL); + + ade9153a_set_reg32(dev, ADE9153A_REG_REACT_NL_LVL, CONFIG_ADE9153A_REACT_NL_LVL); + + ade9153a_set_reg32(dev, ADE9153A_REG_APP_NL_LVL, CONFIG_ADE9153A_APP_NL_LVL); + + ade9153a_set_reg16(dev, ADE9153A_REG_COMPMODE, CONFIG_ADE9153A_COMPMODE); + + ade9153a_set_reg32(dev, ADE9153A_REG_VDIV_RSMALL, CONFIG_ADE9153A_VDIV_RSMALL); + + ade9153a_set_reg16(dev, ADE9153A_REG_EP_CFG, CONFIG_ADE9153A_EP_CFG); + + ade9153a_set_reg16(dev, ADE9153A_REG_EGY_TIME, CONFIG_ADE9153A_EGY_TIME); + + LOG_DBG("Initial setup...[ok]"); + + return 0; +} + +static int ade9153a_init(const struct device *dev) +{ + const struct ade9153a_config *cfg = dev->config; + + if (!device_is_ready(cfg->spi_dt_spec.bus)) { + LOG_DBG("Bus device is not ready"); + return -EINVAL; + } + + if (!gpio_is_ready_dt(&cfg->reset_gpio_dt_spec)) { + LOG_DBG("%s: device %s is not ready", dev->name, + cfg->reset_gpio_dt_spec.port->name); + return -ENODEV; + } + + gpio_pin_configure_dt(&cfg->reset_gpio_dt_spec, + GPIO_OUTPUT | cfg->reset_gpio_dt_spec.dt_flags); + + gpio_pin_set_dt(&cfg->reset_gpio_dt_spec, 0); + + ade9153a_reset(dev); + + if (ade9153a_probe(dev) < 0) { + return -ENODEV; + } + +#if defined(CONFIG_ADE9153A_SETUP_ON_STARTUP) + ade9153a_setup(dev); +#endif /* CONFIG_ADE9153A_SETUP_ON_STARTUP */ + +#if defined(CONFIG_ADE9153A_ACAL_ON_STARTUP) + ade9153a_start_autocalibration(dev); +#endif /* CONFIG_ADE9153A_ACAL_ON_STARTUP */ + +#if defined(CONFIG_ADE9153A_TRIGGER) + ade9153a_init_interrupt(dev); +#endif /* CONFIG_ADE9153A_TRIGGER */ + + return 0; +} + +#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0 +#warning "ADE9153A driver enabled without any devices" +#endif + +/* clang-format off */ +#define ADE9153A_DEFINE(inst) \ + static struct ade9153a_data ade9153a_data_##inst; \ + \ + static const struct ade9153a_config ade9153a_config_##inst = { \ + .spi_dt_spec = SPI_DT_SPEC_INST_GET( \ + inst, (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8)), 0), \ + .reset_gpio_dt_spec = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}), \ + IF_ENABLED(CONFIG_ADE9153A_TRIGGER, ( \ + .irq_gpio_dt_spec = GPIO_DT_SPEC_INST_GET_OR(inst, irq_gpios, {0}), \ + .cf_gpio_dt_spec = GPIO_DT_SPEC_INST_GET_OR(inst, cf_gpios, {0}), \ + ) \ + )}; \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, ade9153a_init, NULL, &ade9153a_data_##inst, \ + &ade9153a_config_##inst, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &ade9153a_driver_api); + +/* clang-format on */ + +DT_INST_FOREACH_STATUS_OKAY(ADE9153A_DEFINE) diff --git a/drivers/sensor/adi/ade9153a/ade9153a.h b/drivers/sensor/adi/ade9153a/ade9153a.h new file mode 100644 index 0000000000000..155c8a18d9d5f --- /dev/null +++ b/drivers/sensor/adi/ade9153a/ade9153a.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025 Plentify (Pty) Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_ADI_ADE9153A_H +#define ZEPHYR_DRIVERS_SENSOR_ADI_ADE9153A_H + +#include +#include +#include + +#define DECODE_Q1_31(_var) \ + (((double)(CONFIG_ADE9153A_CAL_##_var##_CC) / INT32_MAX) * \ + (1U << CONFIG_ADE9153A_CAL_##_var##_CC_SHIFT)) + +#define CAL_IRMS_CC DECODE_Q1_31(IRMS) /* (uA/code) */ +#define CAL_VRMS_CC DECODE_Q1_31(VRMS) /* (uV/code) */ +/* (uW/code) Applicable for Active, reactive and apparent power */ +#define CAL_POWER_CC DECODE_Q1_31(POWER) +/* (uWhr/xTHR_HI code)Applicable for Active, reactive and apparent energy */ +#define CAL_ENERGY_CC DECODE_Q1_31(ENERGY) + +struct ade9153a_data { + int32_t active_energy_reg; + int32_t fund_reactive_energy_reg; + int32_t apparent_energy_reg; + int32_t active_power_reg; + int32_t fund_reactive_power_reg; + int32_t apparent_power_reg; + int32_t current_rms_reg; + int32_t voltage_rms_reg; + int32_t half_current_rms_reg; + int32_t half_voltage_rms_reg; + int32_t power_factor_reg; + int32_t period_reg; + int16_t acc_mode_reg; + int32_t angle_reg_av_ai_reg; + uint32_t temperature_trim; + uint16_t temperature_offset; + uint32_t temperature_gain; + uint16_t temperature_reg; + union ade9153a_status_reg status_reg; + +#ifdef CONFIG_ADE9153A_TRIGGER + const struct device *dev; + struct gpio_callback gpio_cb; + + sensor_trigger_handler_t irq_handler; + const struct sensor_trigger *irq_trigger; + sensor_trigger_handler_t cf_handler; + const struct sensor_trigger *cf_trigger; + + K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_ADE9153A_THREAD_STACK_SIZE); + struct k_msgq trigger_pins_msgq; + char trigger_msgq_buffer[10 * sizeof(uint32_t)]; + struct k_thread thread; +#endif /* CONFIG_ADE9153A_TRIGGER */ +}; + +struct ade9153a_config { + struct spi_dt_spec spi_dt_spec; + struct gpio_dt_spec reset_gpio_dt_spec; +#ifdef CONFIG_ADE9153A_TRIGGER + struct gpio_dt_spec cf_gpio_dt_spec; + struct gpio_dt_spec irq_gpio_dt_spec; +#endif +}; + +#if defined(CONFIG_ADE9153A_TRIGGER) || defined(__DOXYGEN__) +/** + * @brief Set the trigger for an specific GPIO. + * + * This sensor generates interruptions related to IRQ and CF pins. + * + * @param dev The sensor device. + * @param trig Trigger to set. + * @param handler The handler function to be called. + * @retval 0 on success. + */ +int ade9153a_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler); + +int ade9153a_init_interrupt(const struct device *dev); +#endif /* CONFIG_ADE9153A_TRIGGER */ + +#endif /* end ifndef ZEPHYR_DRIVERS_SENSOR_ADI_ADE9153A_H */ diff --git a/drivers/sensor/adi/ade9153a/ade9153a_trigger.c b/drivers/sensor/adi/ade9153a/ade9153a_trigger.c new file mode 100644 index 0000000000000..da67a81613dbb --- /dev/null +++ b/drivers/sensor/adi/ade9153a/ade9153a_trigger.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2025 Plentify (Pty) Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT adi_ade9153a + +#include +#include +#include +#include +#include + +#include "ade9153a.h" + +#include +LOG_MODULE_DECLARE(ade9153a, CONFIG_SENSOR_LOG_LEVEL); + +BUILD_ASSERT( + CONFIG_ADE9153A_TRIGGER_IRQ || CONFIG_ADE9153A_TRIGGER_CF, + "At least one of them must be enabled if the trigger function is enabled for this driver."); + +static inline void interrupt_set_enable(const struct ade9153a_config *cfg, bool enable) +{ +#if defined(CONFIG_ADE9153A_TRIGGER_IRQ) + gpio_pin_interrupt_configure_dt(&cfg->irq_gpio_dt_spec, + enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE); +#endif +#if defined(CONFIG_ADE9153A_TRIGGER_CF) + gpio_pin_interrupt_configure_dt(&cfg->cf_gpio_dt_spec, + enable ? GPIO_INT_EDGE_TO_ACTIVE : GPIO_INT_DISABLE); +#endif +} + +static void gpio_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + struct ade9153a_data *data = CONTAINER_OF(cb, struct ade9153a_data, gpio_cb); + const struct ade9153a_config *config = data->dev->config; + + interrupt_set_enable(config, false); + + k_msgq_put(&data->trigger_pins_msgq, &pins, K_NO_WAIT); +} + +static void thread_cb(const struct device *dev, uint32_t pins) +{ + struct ade9153a_data *data = dev->data; + const struct ade9153a_config *config = dev->config; + +#if defined(CONFIG_ADE9153A_TRIGGER_IRQ) + if ((pins & BIT(config->irq_gpio_dt_spec.pin)) && (data->irq_handler != NULL)) { + LOG_DBG("IRQ trigger happened"); + data->irq_handler(dev, data->irq_trigger); + } +#endif + +#if defined(CONFIG_ADE9153A_TRIGGER_CF) + if ((pins & BIT(config->cf_gpio_dt_spec.pin)) && (data->cf_handler != NULL)) { + LOG_DBG("CF trigger happened"); + data->cf_handler(dev, data->cf_trigger); + } + +#endif + interrupt_set_enable(config, true); +} + +static void thread(void *dev_data, void *p2, void *p3) +{ + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + __ASSERT_NO_MSG(dev_data != NULL); + + struct ade9153a_data *data = dev_data; + int32_t trigger_pins; + + while (1) { + k_msgq_get(&data->trigger_pins_msgq, &trigger_pins, K_FOREVER); + thread_cb(data->dev, trigger_pins); + } +} + +int ade9153a_trigger_set(const struct device *dev, const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + __ASSERT_NO_MSG(trig->chan == SENSOR_CHAN_ALL); + LOG_DBG("trig->chan: %d", trig->chan); + LOG_DBG("trig->type: %d", trig->type); + struct ade9153a_data *data = dev->data; + const struct ade9153a_config *config = dev->config; + enum sensor_trigger_ade9153a ade_trig_type = (enum sensor_trigger_ade9153a)trig->type; + +#if defined(CONFIG_ADE9153A_TRIGGER_IRQ) + if (config->irq_gpio_dt_spec.port == NULL) { + return -ENOTSUP; + } +#endif + +#if defined(CONFIG_ADE9153A_TRIGGER_CF) + if (config->cf_gpio_dt_spec.port == NULL) { + return -ENOTSUP; + } +#endif + + interrupt_set_enable(config, false); + + switch (ade_trig_type) { + case SENSOR_TRIG_ADE9153A_IRQ: + data->irq_handler = handler; + data->irq_trigger = trig; + LOG_DBG("IRQ trigger set"); + break; + case SENSOR_TRIG_ADE9153A_CF: + data->cf_handler = handler; + data->cf_trigger = trig; + LOG_DBG("CF trigger set"); + break; + default: + LOG_ERR("Unsupported sensor trigger"); + return -EINVAL; + } + + interrupt_set_enable(config, true); + + return 0; +} + +int ade9153a_init_interrupt(const struct device *dev) +{ + struct ade9153a_data *data = dev->data; + const struct ade9153a_config *config = dev->config; + +#if defined(CONFIG_ADE9153A_TRIGGER_IRQ) + if (!gpio_is_ready_dt(&config->irq_gpio_dt_spec)) { + LOG_ERR("%s: device %s is not ready", dev->name, + config->irq_gpio_dt_spec.port->name); + return -ENODEV; + } + + gpio_pin_configure_dt(&config->irq_gpio_dt_spec, + GPIO_INPUT | config->irq_gpio_dt_spec.dt_flags); + + gpio_init_callback(&data->gpio_cb, gpio_callback, BIT(config->irq_gpio_dt_spec.pin)); + + if (gpio_add_callback(config->irq_gpio_dt_spec.port, &data->gpio_cb) < 0) { + LOG_DBG("Failed to set IRQ gpio callback!"); + return -EIO; + } + LOG_DBG("IRQ trigger initialized"); +#endif + +#if defined(CONFIG_ADE9153A_TRIGGER_CF) + if (!gpio_is_ready_dt(&config->cf_gpio_dt_spec)) { + LOG_ERR("%s: device %s is not ready", dev->name, + config->cf_gpio_dt_spec.port->name); + return -ENODEV; + } + + gpio_pin_configure_dt(&config->cf_gpio_dt_spec, + GPIO_INPUT | config->cf_gpio_dt_spec.dt_flags); + + gpio_init_callback(&data->gpio_cb, gpio_callback, BIT(config->cf_gpio_dt_spec.pin)); + + if (gpio_add_callback(config->cf_gpio_dt_spec.port, &data->gpio_cb) < 0) { + LOG_DBG("Failed to set CF gpio callback!"); + return -EIO; + } + LOG_DBG("CF trigger initialized"); +#endif + + data->dev = dev; + + k_msgq_init(&data->trigger_pins_msgq, data->trigger_msgq_buffer, sizeof(uint32_t), 10); + + k_tid_t tid = k_thread_create(&data->thread, data->thread_stack, + CONFIG_ADE9153A_THREAD_STACK_SIZE, thread, data, NULL, NULL, + K_PRIO_COOP(CONFIG_ADE9153A_THREAD_PRIORITY), 0, K_NO_WAIT); + + if (IS_ENABLED(CONFIG_THREAD_NAME)) { + k_thread_name_set(tid, "ade9153a_thread"); + } + + interrupt_set_enable(config, true); + + return 0; +} diff --git a/drivers/sensor/adi/ade9153a/scripts/check_cal_constants.c b/drivers/sensor/adi/ade9153a/scripts/check_cal_constants.c new file mode 100644 index 0000000000000..5e84b61f412b1 --- /dev/null +++ b/drivers/sensor/adi/ade9153a/scripts/check_cal_constants.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Plentify (Pty) Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define CONFIG_ADE9153A_CAL_IRMS_CC 1799999318 +#define CONFIG_ADE9153A_CAL_IRMS_CC_SHIFT 0 +#define CONFIG_ADE9153A_CAL_VRMS_CC 1800000660 +#define CONFIG_ADE9153A_CAL_VRMS_CC_SHIFT 4 +#define CONFIG_ADE9153A_CAL_POWER_CC 1582031699 +#define CONFIG_ADE9153A_CAL_POWER_CC_SHIFT 11 +#define CONFIG_ADE9153A_CAL_ENERGY_CC 1843200246 +#define CONFIG_ADE9153A_CAL_ENERGY_CC_SHIFT 0 + +#define DECODE_Q1_31(_var) \ + (((double)(CONFIG_ADE9153A_CAL_##_var##_CC) / INT32_MAX) * \ + (1U << CONFIG_ADE9153A_CAL_##_var##_CC_SHIFT)) + +#define CAL_IRMS_CC DECODE_Q1_31(IRMS) /* (uA/code) */ +#define CAL_VRMS_CC DECODE_Q1_31(VRMS) /* (uV/code) */ +/* (uW/code) Applicable for Active, reactive and apparent power */ +#define CAL_POWER_CC DECODE_Q1_31(POWER) +/* (uWhr/xTHR_HI code)Applicable for Active, reactive and apparent energy */ +#define CAL_ENERGY_CC DECODE_Q1_31(ENERGY) + +int main(int argc, char *argv[]) +{ + /* Original value of CAL_IRMS_CC is 0.838190 */ + printf("CAL_IRMS_CC=%.09f\n", CAL_IRMS_CC); + /* Original value of CAL_VRMS_CC is 13.41105*/ + printf("CAL_VRMS_CC=%.09f\n", CAL_VRMS_CC); + /* Original value of CAL_POWER_CC is 1508.743*/ + printf("CAL_POWER_CC=%.09f\n", CAL_POWER_CC); + /* Original value of CAL_ENERGY_CC is 0.858307*/ + printf("CAL_ENERGY_CC=%.09f\n", CAL_ENERGY_CC); + return 0; +} diff --git a/drivers/sensor/adi/ade9153a/scripts/q31.py b/drivers/sensor/adi/ade9153a/scripts/q31.py new file mode 100644 index 0000000000000..6c0b0e26b709b --- /dev/null +++ b/drivers/sensor/adi/ade9153a/scripts/q31.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2025 Plentify (Pty) Ltd. +# +# SPDX-License-Identifier: Apache-2.0 + + +class Q31(object): + def __init__(self, value, shift) -> None: + self.value = int(value) + self.shift = int(shift) + + def __repr__(self): + return f"Q31(value={self.value}, shift={self.shift})" + + @staticmethod + def encode(n: float) -> "Q31": + """ + >>> Q31.encode(0.98) + Q31(value=2104533974, shift=0) + >>> Q31.encode(8.98) + Q31(value=1205275196, shift=4) + >>> Q31.encode(-538.1928462) + Q31(value=-1128672203, shift=10) + """ + temp = n + shift = 0 + while not -1.0 <= temp < 1.0: + shift += 1 + temp = n / float(1 << shift) + return Q31(temp * 0x7FFFFFFF, shift) + + def decode(self) -> float: + return (self.value / 0x7FFFFFFF) * float(1 << self.shift) + + def isclose(self, f: float) -> bool: + """Compare the floats. + + >>> q = Q31.encode(0.98) + >>> q.isclose(0.98) + True + >>> q.isclose(0.9812345) + False + >>> Q31.encode(65432.123456).isclose(65432.123456) + True + """ + import math + + fraction_size = len(str(f).split(".")[1]) + + return math.isclose(self.decode(), f, rel_tol=(1 / 10**fraction_size), abs_tol=0.0) + + +def test(): + import doctest + + doctest.testmod() + + +def arg_parse_handler(): + import argparse + + parent_parser = argparse.ArgumentParser(allow_abbrev=False, add_help=False) + parent_parser.add_argument( + "--debug", + default=False, + required=False, + action="store_true", + dest="debug", + help="debug flag", + ) + main_parser = argparse.ArgumentParser(allow_abbrev=False) + service_subparsers = main_parser.add_subparsers(title="sub_command", dest="sub_command") + service_subparsers.add_parser("test", help="test") + decode_parser = service_subparsers.add_parser("decode", help="Decode q1.31 to float") + decode_parser.add_argument("q1_31", help="the q1.31 value", type=int) + decode_parser.add_argument("shift", help="the shift calculated on the encode phase", type=int) + + encode_parser = service_subparsers.add_parser( + "encode", + help="Encode float to q1.31 with scale factor (shift)", + parents=[parent_parser], + ) + encode_parser.add_argument("value", help="the float value to be encoded", type=float) + return main_parser.parse_args() + + +if __name__ == "__main__": + args = arg_parse_handler() + + if args.sub_command == "test": + print("Executing unittests") + test() + + if args.sub_command == "encode": + print(Q31.encode(args.value)) + + if args.sub_command == "decode": + print(Q31(args.q1_31, args.shift).decode()) diff --git a/dts/bindings/sensor/adi,ade9153a-common.yaml b/dts/bindings/sensor/adi,ade9153a-common.yaml new file mode 100644 index 0000000000000..dcc7076f3e2c6 --- /dev/null +++ b/dts/bindings/sensor/adi,ade9153a-common.yaml @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Plentify (Pty) Ltd. +# SPDX-License-Identifier: Apache-2.0 + +description: ADE9153a Energy Metering IC with Autocalibration + +include: sensor-device.yaml + +properties: + cf-gpios: + type: phandle-array + description: | + The CF1 pin is a Calibration Frequency (CF) Logic Output. The CF1 and CF2 outputs provide + proportional power information based on the CFxSEL bits in the CFMODE register. Use these + outputs for operational and calibration purposes. Scale the full-scale output frequency by + writing to the CFxDEN registers, respectively. + + irq-gpios: + type: phandle-array + description: | + Interrupt Request Output (IRQn). This pin is an active low logic output. See the + Interrupts/Events section for information about events that trigger interrupts.. + + reset-gpios: + type: phandle-array + description: | + RESETn is an Active Low Reset Input. To initiate a hardware reset, this pin must be brought + low for a minimum of 10 μs. diff --git a/dts/bindings/sensor/adi,ade9153a-spi.yaml b/dts/bindings/sensor/adi,ade9153a-spi.yaml new file mode 100644 index 0000000000000..fde2c022f7052 --- /dev/null +++ b/dts/bindings/sensor/adi,ade9153a-spi.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Plentify (Pty) Ltd. +# SPDX-License-Identifier: Apache-2.0 + +description: ADE9153A + +compatible: "adi,ade9153a" + +include: ["spi-device.yaml", "adi,ade9153a-common.yaml"] diff --git a/include/zephyr/drivers/sensor/ade9153a.h b/include/zephyr/drivers/sensor/ade9153a.h new file mode 100644 index 0000000000000..bbbd79aae2470 --- /dev/null +++ b/include/zephyr/drivers/sensor/ade9153a.h @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2025 Plentify (Pty) Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ZEPHYR_DRIVERS_SENSOR_ADE9153A_H_ +#define _ZEPHYR_DRIVERS_SENSOR_ADE9153A_H_ + +#include +#include + +#define MASK_ADE9153A 0xFFFFU +/* Phase A current gain adjust. */ +#define ADE9153A_REG_AIGAIN 0x0000U +/* Phase A phase correction factor. */ +#define ADE9153A_REG_APHASECAL 0x0001U +/* Phase A voltage gain adjust. */ +#define ADE9153A_REG_AVGAIN 0x0002U +/* Phase A current rms offset for filter-based AIRMS calculation. */ +#define ADE9153A_REG_AIRMS_OS 0x0003U +/* Phase A voltage rms offset for filter-based AVRMS calculation. */ +#define ADE9153A_REG_AVRMS_OS 0x0004U +/* Phase A power gain adjust for AWATT, AVA, and AFVAR calculations. */ +#define ADE9153A_REG_APGAIN 0x0005U +/* Phase A total active power offset correction for AWATT calculation. */ +#define ADE9153A_REG_AWATT_OS 0x0006U +/* Phase A fundamental reactive power offset correction for AFVAR calculation. */ +#define ADE9153A_REG_AFVAR_OS 0x0007U +/* Phase A voltage rms offset for fast rms, AVRMS_OC calculation. */ +#define ADE9153A_REG_AVRMS_OC_OS 0x0008U +/* Phase A current rms offset for fast rms, AIRMS_OC calculation. */ +#define ADE9153A_REG_AIRMS_OC_OS 0x0009U +/* Phase B current gain adjust. */ +#define ADE9153A_REG_BIGAIN 0x0010U +/* Phase B current rms offset for filter-based BIRMS calculation. */ +#define ADE9153A_REG_BIRMS_OS 0x0013U +/* Phase B current rms offset for fast rms, BIRMS_OC calculation. */ +#define ADE9153A_REG_BIRMS_OC_OS 0x0019U +/* DSP configuration register. */ +#define ADE9153A_REG_CONFIG0 0x0020U +/* Nominal phase voltage rms used in the calculation of apparent power, AVA, when the VNOMA_EN */ +/* bit is set in the CONFIG0 register. */ +#define ADE9153A_REG_VNOM 0x0021U +/* Value used in the digital integrator algorithm. If the integrator is turned on, with INTEN_BI + * equal to 1 in the CONFIG0 register, it is recommended to leave this register at the default + * value. + */ +#define ADE9153A_REG_DICOEFF 0x0022U +/* PGA gain for Current Channel B ADC. */ +#define ADE9153A_REG_BI_PGAGAIN 0x0023U +/* MSure autocalibration configuration register. */ +#define ADE9153A_REG_MS_ACAL_CFG 0x0030U +/* Phase delay of the CT used on Current Channel B. This register is in 5.27 format and expressed in + * degrees. + */ +#define ADE9153A_REG_CT_PHASE_DELAY 0x0049U +/* Corner frequency of the CT. This value is calculated from the CT_PHASE_DELAY value. */ +#define ADE9153A_REG_CT_CORNER 0x004AU +/* This register holds the resistance value, in Ω, of the small resistor in the resistor divider.*/ +#define ADE9153A_REG_VDIV_RSMALL 0x004CU +/* Instantaneous Current Channel A waveform processed by the DSP, at 4kSPS. */ +#define ADE9153A_REG_AI_WAV 0x0200U +/* Instantaneous Voltage Channel waveform processed by the DSP, at 4kSPS. */ +#define ADE9153A_REG_AV_WAV 0x0201U +/* Phase A filter-based current rms value updated at 4kSPS. */ +#define ADE9153A_REG_AIRMS 0x0202U +/* Phase A filter-based voltage rms value updated at 4kSPS. */ +#define ADE9153A_REG_AVRMS 0x0203U +/* Phase A low-pass filtered total active power updated at 4kSPS. */ +#define ADE9153A_REG_AWATT 0x0204U +/* Phase A total apparent power updated at 4kSPS. */ +#define ADE9153A_REG_AVA 0x0206U +/* Phase A fundamental reactive power updated at 4kSPS. */ +#define ADE9153A_REG_AFVAR 0x0207U +/* Phase A power factor updated at 1.024 sec. */ +#define ADE9153A_REG_APF 0x0208U +/* Phase A current fast rms calculation; one cycle rms updated every half cycle. */ +#define ADE9153A_REG_AIRMS_OC 0x0209U +/* Phase A voltage fast rms calculation; one cycle rms updated every half cycle. */ +#define ADE9153A_REG_AVRMS_OC 0x020AU +/* Instantaneous Phase B Current Channel waveform processed by the DSP at 4kSPS. */ +#define ADE9153A_REG_BI_WAV 0x0210U +/* Phase B filter-based current rms value updated at 4kSPS. */ +#define ADE9153A_REG_BIRMS 0x0212U +/* Phase B Current fast rms calculation; one cycle rms updated every half cycle. */ +#define ADE9153A_REG_BIRMS_OC 0x0219U +/* Current Channel A mSure CC estimation from autocalibration. */ +#define ADE9153A_REG_MS_ACAL_AICC 0x0220U +/* Current Channel A mSure certainty of autocalibration. */ +#define ADE9153A_REG_MS_ACAL_AICERT 0x0221U +/* Current Channel B mSure CC estimation from autocalibration. */ +#define ADE9153A_REG_MS_ACAL_BICC 0x0222U +/* Current Channel B mSure certainty of autocalibration. */ +#define ADE9153A_REG_MS_ACAL_BICERT 0x0223U +/* Voltage Channel mSure CC estimation from autocalibration. */ +#define ADE9153A_REG_MS_ACAL_AVCC 0x0224U +/* Voltage Channel mSure certainty of autocalibration. */ +#define ADE9153A_REG_MS_ACAL_AVCERT 0x0225U +/* The MS_STATUS_CURRENT register contains bits that reflect the present state of the mSure system. + */ +#define ADE9153A_REG_MS_STATUS_CURRENT 0x0240U +/* This register indicates the version of the ADE9153A DSP after the user writes RUN=1 to start + * measurements. + */ +#define ADE9153A_REG_VERSION_DSP 0x0241U +/* This register indicates the version of the product being used. */ +#define ADE9153A_REG_VERSION_PRODUCT 0x0242U +/* Phase A accumulated total active power; updated after PWR_TIME 4kSPS samples. */ +#define ADE9153A_REG_AWATT_ACC 0x039DU +/* Phase A accumulated total active energy, least significant bits (LSBs). Updated according to the + * settings in the EP_CFG and EGY_TIME registers. + */ +#define ADE9153A_REG_AWATTHR_LO 0x039EU +/* Phase A accumulated total active energy, most significant bits (MSBs). Updated according to the + * settings in the EP_CFG and EGY_TIME registers. + */ +#define ADE9153A_REG_AWATTHR_HI 0x039FU +/* Phase A accumulated total apparent power; updated after PWR_TIME 4kSPS samples. */ +#define ADE9153A_REG_AVA_ACC 0x03B1U +/* Phase A accumulated total apparent energy, LSBs. Updated according to the settings in the EP_CFG + * and EGY_TIME registers. + */ +#define ADE9153A_REG_AVAHR_LO 0x03B2U +/* Phase A accumulated total apparent energy, MSBs. Updated according to the settings in the EP_CFG + * and EGY_TIME registers. + */ +#define ADE9153A_REG_AVAHR_HI 0x03B3U +/* Phase A accumulated fundamental reactive power; updated after PWR_TIME 4kSPS samples. */ +#define ADE9153A_REG_AFVAR_ACC 0x03BBU +/* Phase A accumulated fundamental reactive energy, LSBs. Updated according to the settings in the + * EP_CFG and EGY_TIME registers. + */ +#define ADE9153A_REG_AFVARHR_LO 0x03BCU +/* Phase A accumulated fundamental reactive energy, MSBs. Updated according to the settings in the + * EP_CFG and EGY_TIME registers. + */ +#define ADE9153A_REG_AFVARHR_HI 0x03BDU +/* Accumulated positive total active power from the AWATT register; updated after PWR_TIME 4 kSPS + * samples. + */ +#define ADE9153A_REG_PWATT_ACC 0x03EBU +/* Accumulated negative total active power from the AWATT register; updated after PWR_TIME 4 kSPS + * samples. + */ +#define ADE9153A_REG_NWATT_ACC 0x03EFU +/* Accumulated positive fundamental reactive power from the AFVAR register; updated after PWR_TIME 4 + * kSPS samples. + */ +#define ADE9153A_REG_PFVAR_ACC 0x03F3U +/* Accumulated negative fundamental reactive power from the AFVAR register, updated after PWR_TIME 4 + * kSPS samples. + */ +#define ADE9153A_REG_NFVAR_ACC 0x03F7U +/* Current peak register. */ +#define ADE9153A_REG_IPEAK 0x0400U +/* Voltage peak register. */ +#define ADE9153A_REG_VPEAK 0x0401U +/* Tier 1 interrupt status register. */ +#define ADE9153A_REG_STATUS 0x0402U +/* Tier 1 interrupt enable register. */ +#define ADE9153A_REG_MASK 0x0405U +/* Overcurrent RMS_OC detection threshold level. */ +#define ADE9153A_REG_OI_LVL 0x0409U +/* Phase A overcurrent RMS_OC value. If overcurrent detection on this channel is enabled with OIA_EN + * in the CONFIG3 register and AIRMS_OC is greater than the OILVL threshold, this value is updated. + */ +#define ADE9153A_REG_OIA 0x040AU +/* Phase B overcurrent RMS_OC value. See the OIA description. */ +#define ADE9153A_REG_OIB 0x040BU +/* User configured line period value used for RMS_OC when the UPERIOD_SEL bit in the CONFIG2 + * register is set. + */ +#define ADE9153A_REG_USER_PERIOD 0x040EU +/* Register used in the algorithm that computes the fundamental reactive power. */ +#define ADE9153A_REG_VLEVEL 0x040FU +/* Voltage RMS_OC dip detection threshold level. */ +#define ADE9153A_REG_DIP_LVL 0x0410U +/* Phase A voltage RMS_OC value during a dip condition. */ +#define ADE9153A_REG_DIPA 0x0411U +/* Voltage RMS_OC swell detection threshold level. */ +#define ADE9153A_REG_SWELL_LVL 0x0414U +/* Phase A voltage RMS_OC value during a swell condition. */ +#define ADE9153A_REG_SWELLA 0x0415U +/* Line period on the Phase A voltage. */ +#define ADE9153A_REG_APERIOD 0x0418U +/* No load threshold in the total active power datapath. */ +#define ADE9153A_REG_ACT_NL_LVL 0x041CU +/* No load threshold in the fundamental reactive power datapath. */ +#define ADE9153A_REG_REACT_NL_LVL 0x041DU +/* No load threshold in the total apparent power datapath. */ +#define ADE9153A_REG_APP_NL_LVL 0x041EU +/* Phase no load register. */ +#define ADE9153A_REG_PHNOLOAD 0x041FU +/* Sets the maximum output rate from the digital to frequency converter of the total active power + * for the CF calibration pulse output. It is recommended to leave this at WTHR = 0x00100000. + */ +#define ADE9153A_REG_WTHR 0x0420U +/* See WTHR. It is recommended to leave this value at VARTHR = 0x00100000. */ +#define ADE9153A_REG_VARTHR 0x0421U +/* See WTHR. It is recommended to leave this value at VATHR = 0x00100000. */ +#define ADE9153A_REG_VATHR 0x0422U +/* This register holds the data read or written during the last 32-bit transaction on the SPI port. + */ +#define ADE9153A_REG_LAST_DATA_32 0x0423U +/* CF calibration pulse width configuration register. */ +#define ADE9153A_REG_CF_LCFG 0x0425U +/* Temperature sensor gain and offset, calculated during the manufacturing process. */ +#define ADE9153A_REG_TEMP_TRIM 0x0471U +/* Chip identification, 32 MSBs. */ +#define ADE9153A_REG_CHIP_ID_HI 0x0472U +/* Chip identification, 32 LSBs. */ +#define ADE9153A_REG_CHIP_ID_LO 0x0473U + +/* 16-bit below */ +/* Write this register to 1 to start the measurements. */ +#define ADE9153A_REG_RUN 0x0480U +/* Configuration Register 1. */ +#define ADE9153A_REG_CONFIG1 0x0481U +/* Time between positive to negative zero crossings on Phase A voltage and current. */ +#define ADE9153A_REG_ANGL_AV_AI 0x0485U +/* Time between positive to negative zero crossings on Phase A and Phase B currents. */ +#define ADE9153A_REG_ANGL_AI_BI 0x0488U +/* Voltage RMS_OC dip detection cycle configuration. */ +#define ADE9153A_REG_DIP_CYC 0x048BU +/* Voltage RMS_OC swell detection cycle configuration. */ +#define ADE9153A_REG_SWELL_CYC 0x048CU +/* CFx configuration register. */ +#define ADE9153A_REG_CFMODE 0x0490U +/* Computation mode register. Set this register to 0x0005. */ +#define ADE9153A_REG_COMPMODE 0x0491U +/* Accumulation mode register. */ +#define ADE9153A_REG_ACCMODE 0x0492U +/* Configuration Register 3 for configuration of power quality settings. */ +#define ADE9153A_REG_CONFIG3 0x0493U +/* CF1 denominator register. */ +#define ADE9153A_REG_CF1DEN 0x0494U +/* CF2 denominator register. */ +#define ADE9153A_REG_CF2DEN 0x0495U +/* Zero-crossing timeout configuration register. */ +#define ADE9153A_REG_ZXTOUT 0x0498U +/* Voltage channel zero-crossing threshold register. */ +#define ADE9153A_REG_ZXTHRSH 0x0499U +/* Zero-crossing detection configuration register. */ +#define ADE9153A_REG_ZX_CFG 0x049AU +/* Power sign register. */ +#define ADE9153A_REG_PHSIGN 0x049DU +/* This register holds the CRC of the configuration registers. */ +#define ADE9153A_REG_CRC_RSLT 0x04A8U +/* The register holds the 16-bit CRC of the data sent out on the MOSI/RX pin during the last SPI + * register read. + */ +#define ADE9153A_REG_CRC_SPI 0x04A9U +/* This register holds the data read or written during the last 16-bit transaction on the SPI port. + * When using UART, this register holds the lower 16 bits of the last data read or write. + */ +#define ADE9153A_REG_LAST_DATA_16 0x04ACU +/* This register holds the address and the read/write operation request (CMD_HDR) for the last + * transaction on the SPI port. + */ +#define ADE9153A_REG_LAST_CMD 0x04AEU +/* Configuration Register 2. This register controls the high-pass filter (HPF) corner and the user + * period selection. + */ +#define ADE9153A_REG_CONFIG2 0x04AFU +/* Energy and power accumulation configuration. */ +#define ADE9153A_REG_EP_CFG 0x04B0U +/* Power update time configuration. */ +#define ADE9153A_REG_PWR_TIME 0x04B1U +/* Energy accumulation update time configuration. */ +#define ADE9153A_REG_EGY_TIME 0x04B2U +/* This register forces an update of the CRC of configuration registers. */ +#define ADE9153A_REG_CRC_FORCE 0x04B4U +/* Temperature sensor configuration register. */ +#define ADE9153A_REG_TEMP_CFG 0x04B6U +/* Temperature measurement result. */ +#define ADE9153A_REG_TEMP_RSLT 0x04B7U +/* This register configures the PGA gain for Current Channel A. */ +#define ADE9153A_REG_AI_PGAGAIN 0x04B9U +/* This register enables the configuration lock feature. */ +#define ADE9153A_REG_WR_LOCK 0x04BFU +/* Tier 2 status register for the autocalibration and monitoring mSure system related interrupts. + * Any bit set in this register causes the corresponding bit in the status register to be set. This + * register is cleared on a read and all bits are reset. If a new status bit arrives on the same + * clock on which the read occurs, the new status bit remains set; in this way, no status bit is + * missed. + */ +#define ADE9153A_REG_MS_STATUS_IRQ 0x04C0U +/* Tier 2 status register for power quality event related interrupts. See the MS_STATUS_IRQ + * description. + */ +#define ADE9153A_REG_EVENT_STATUS 0x04C1U +/* Tier 2 status register for chip error related interrupts. See the MS_STATUS_IRQ description. */ +#define ADE9153A_REG_CHIP_STATUS 0x04C2U +/* This register switches the UART Baud rate between 4800 and 115,200 Baud. Writing a value of + * 0x0052 sets the Baud rate to 115,200 Baud; any other value maintains a Baud rate of 4800. + */ +#define ADE9153A_REG_UART_BAUD_SWITCH 0x04DCU +/* Version of the ADE9153 IC. */ +#define ADE9153A_REG_VERSION 0x04FEU +/* SPI burst read accessible registers organized functionally. See AI_WAV. */ +#define ADE9153A_REG_AI_WAV_1 0x0600U +/* SPI burst read accessible registers organized functionally. See AV_WAV. */ +#define ADE9153A_REG_AV_WAV_1 0x0601U +/* SPI burst read accessible registers organized functionally. See BI_WAV. */ +#define ADE9153A_REG_BI_WAV_1 0x0602U +/* SPI burst read accessible registers organized functionally. See AIRMS. */ +#define ADE9153A_REG_AIRMS_1 0x0604U +/* SPI burst read accessible registers organized functionally. See BIRMS. */ +#define ADE9153A_REG_BIRMS_1 0x0605U +/* SPI burst read accessible registers organized functionally. See AVRMS. */ +#define ADE9153A_REG_AVRMS_1 0x0606U +/* SPI burst read accessible registers organized functionally. See AWATT. */ +#define ADE9153A_REG_AWATT_1 0x0608U +/* SPI burst read accessible registers organized functionally. See AFVAR. */ +#define ADE9153A_REG_AFVAR_1 0x060AU +/* SPI burst read accessible registers organized functionally. See AVA. */ +#define ADE9153A_REG_AVA_1 0x060CU +/* SPI burst read accessible registers organized functionally. See APF. */ +#define ADE9153A_REG_APF_1 0x060EU +/* SPI burst read accessible registers organized by phase. See AI_WAV. */ +#define ADE9153A_REG_AI_WAV_2 0x0610U +/* SPI burst read accessible registers organized by phase. See AV_WAV. */ +#define ADE9153A_REG_AV_WAV_2 0x0611U +/* SPI burst read accessible registers organized by phase. See AIRMS. */ +#define ADE9153A_REG_AIRMS_2 0x0612U +/* SPI burst read accessible registers organized by phase. See AVRMS. */ +#define ADE9153A_REG_AVRMS_2 0x0613U +/* SPI burst read accessible registers organized by phase. See AWATT. */ +#define ADE9153A_REG_AWATT_2 0x0614U +/* SPI burst read accessible registers organized by phase. See AVA. */ +#define ADE9153A_REG_AVA_2 0x0615U +/* SPI burst read accessible registers organized by phase. See AFVAR. */ +#define ADE9153A_REG_AFVAR_2 0x0616U +/* SPI burst read accessible registers organized by phase. See APF. */ +#define ADE9153A_REG_APF_2 0x0617U +/* SPI burst read accessible registers organized by phase. See BI_WAV. */ +#define ADE9153A_REG_BI_WAV_2 0x0618U +/* SPI burst read accessible registers organized by phase. See BIRMS. */ +#define ADE9153A_REG_BIRMS_2 0x061AU + +union ade9153a_status_reg { + struct { /* BITS */ + uint32_t REVAPA: 1; /* [00] */ + uint32_t RESERVED_0: 1; /* [01] */ + uint32_t REVRPA: 1; /* [02] */ + uint32_t RESERVED_1: 1; /* [03] */ + uint32_t REVPCF1: 1; /* [04] */ + uint32_t REVPCF2: 1; /* [05] */ + uint32_t CF1: 1; /* [06] */ + uint32_t CF2: 1; /* [07] */ + uint32_t EGYRDY: 1; /* [08] */ + uint32_t DREADY: 1; /* [09] */ + uint32_t PWRRDY: 1; /* [10] */ + uint32_t RMS_OC_RDY: 1; /* [11] */ + uint32_t TEMP_RDY: 1; /* [12] */ + uint32_t WATTNL: 1; /* [13] */ + uint32_t VANL: 1; /* [14] */ + uint32_t FVARNL: 1; /* [15] */ + uint32_t RSTDONE: 1; /* [16] */ + uint32_t ZXAV: 1; /* [17] */ + uint32_t RESERVED_2: 1; /* [18] */ + uint32_t ZXAI: 1; /* [19] */ + uint32_t ZXBI: 1; /* [20] */ + uint32_t ZXTOAV: 1; /* [21] */ + uint32_t RESERVED_3: 1; /* [22] */ + uint32_t CRC_DONE: 1; /* [23] */ + uint32_t CRC_CHG: 1; /* [24] */ + uint32_t PF_RDY: 1; /* [25] */ + uint32_t RESERVED_4: 1; /* [28:26] */ + uint32_t MS_STAT: 1; /* [29] */ + uint32_t EVENT_STAT: 1; /* [30] */ + uint32_t CHIP_STAT: 1; /* [31] */ + } bits; + uint32_t as_uint32; +}; + +union ade9153a_auto_calibration_cfg_reg { + struct { /* BITS */ + uint32_t ACAL_MODE: 1; /* [00] */ + uint32_t ACAL_RUN: 1; /* [01] */ + uint32_t ACALMODE_AI: 1; /* [02] */ + uint32_t ACALMODE_BI: 1; /* [03] */ + uint32_t AUTOCAL_AI: 1; /* [04] */ + uint32_t AUTOCAL_BI: 1; /* [05] */ + uint32_t AUTOCAL_AV: 1; /* [06] */ + uint32_t RESERVED: 25; /* [31:07] */ + } bits; + uint32_t as_uint32; +}; + +union ade9153a_register { + struct sensor_value as_sensor_value; + struct { + uint32_t value; + uint16_t addr; + uint16_t size; + }; +}; + +enum sensor_trigger_ade9153a { + SENSOR_TRIG_ADE9153A_IRQ = SENSOR_TRIG_PRIV_START, + SENSOR_TRIG_ADE9153A_CF, +}; + +enum sensor_attribute_ade9153a { + SENSOR_ATTR_ADE9153A_REGISTER = SENSOR_ATTR_PRIV_START, + SENSOR_ATTR_ADE9153A_START_AUTOCALIBRATION, +}; + +#endif /* _ZEPHYR_DRIVERS_SENSOR_ADE9153A_H_ */ diff --git a/samples/sensor/ade9153a_shield/CMakeLists.txt b/samples/sensor/ade9153a_shield/CMakeLists.txt new file mode 100644 index 0000000000000..e539e218f6abe --- /dev/null +++ b/samples/sensor/ade9153a_shield/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +set(SHIELD adi_eval_ade9153ashieldz) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ade9153a) + +include_directories("${ZEPHYR_BASE}/drivers/sensor/adi/ade9153a") + +file(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/sensor/ade9153a_shield/Kconfig b/samples/sensor/ade9153a_shield/Kconfig new file mode 100644 index 0000000000000..9111c2f1b8d34 --- /dev/null +++ b/samples/sensor/ade9153a_shield/Kconfig @@ -0,0 +1,13 @@ +# Copyright (c) 2020, Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config LOG + default y + +config LOG_PRINTK + default y + +config SENSOR_LOG_LEVEL + default 4 + +source "Kconfig.zephyr" diff --git a/samples/sensor/ade9153a_shield/README.rst b/samples/sensor/ade9153a_shield/README.rst new file mode 100644 index 0000000000000..5a1b7af05ee08 --- /dev/null +++ b/samples/sensor/ade9153a_shield/README.rst @@ -0,0 +1,186 @@ +.. _bme280: + +BME280 Humidity and Pressure Sensor +################################### + +Overview +******** + +alias b := build +alias f := flash + +build: + west build -p always -b nrf52833dk/nrf52833 samples/sensor/ade9153a_shield/ -- -DSHIELD=adi_ev_ade9153ashieldz + # west build -p always -b nrf5340dk/nrf5340/cpuapp samples/sensor/ade9153a_shield/ -- -DSHIELD=adi_ev_ade9153ashieldz -DCONFIG_NO_OPTIMIZATIONS=y + +flash: build + west flash + +run: flash + tio /dev/tty.usbmodem0006850372581 + +reset: + nrfjprog --reset + +q31: + python drivers/sensor/adi/ade9153a/scripts/q31.py encode 0.838190 # CAL_IRMS_CC + python drivers/sensor/adi/ade9153a/scripts/q31.py encode 13.41105 # CAL_VRMS_CC + python drivers/sensor/adi/ade9153a/scripts/q31.py encode 1508.743 # CAL_POWER_CC + python drivers/sensor/adi/ade9153a/scripts/q31.py encode 0.858307 # CAL_ENERGY_CC + +This sample shows how to use the Zephyr :ref:`sensor_api` API driver for the +`Bosch BME280`_ environmental sensor. + +.. _Bosch BME280: + https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/ + +The sample periodically reads temperature, pressure and humidity data from the +first available BME280 device discovered in the system. The sample checks the +sensor in polling mode (without interrupt trigger). + +Building and Running +******************** + +The sample can be configured to support BME280 sensors connected via either I2C +or SPI. Configuration is done via :ref:`devicetree `. The devicetree +must have an enabled node with ``compatible = "bosch,bme280";``. See +:dtcompatible:`bosch,bme280` for the devicetree binding and see below for +examples and common configurations. + +If the sensor is not built into your board, start by wiring the sensor pins +according to the connection diagram given in the `BME280 datasheet`_ at +page 38. + +.. _BME280 datasheet: + https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf + +Boards with a built-in BME280 +============================= + +Your board may have a BME280 node configured in its devicetree by default. Make +sure this node has ``status = "okay";``, then build and run with: + +.. zephyr-app-commands:: + :zephyr-app: samples/sensor/bme280 + :goals: build flash + :board: adafruit_feather_m0_basic_proto + +BME280 via Arduino SPI pins +=========================== + +If you wired the sensor to a SPI peripheral on an Arduino header, build and +flash with: + +.. zephyr-app-commands:: + :zephyr-app: samples/sensor/bme280 + :goals: build flash + :gen-args: -DDTC_OVERLAY_FILE=arduino_spi.overlay + +The devicetree overlay :zephyr_file:`samples/sensor/bme280/arduino_spi.overlay` +works on any board with a properly configured Arduino pin-compatible SPI +peripheral. + +BME280 via Arduino I2C pins +=========================== + +If you wired the sensor to an I2C peripheral on an Arduino header, build and +flash with: + +.. zephyr-app-commands:: + :zephyr-app: samples/sensor/bme280 + :goals: build flash + :gen-args: -DDTC_OVERLAY_FILE=arduino_i2c.overlay + +The devicetree overlay :zephyr_file:`samples/sensor/bme280/arduino_i2c.overlay` +works on any board with a properly configured Arduino pin-compatible I2C +peripheral. + +BME280 via Raspberry Pi Pico +============================ + +The default assignment of the built-in spi0 device on the :ref:`rpi_pico` is +to GPIO16 through GPIO19. With the sensor wired to those lines, build and +flash with: + +.. zephyr-app-commands:: + :zephyr-app: samples/sensor/bme280 + :goals: build flash + :board: rpi_pico + +An alternative is to use PIO serving as an SPI device. The devicetree +overlay :zephyr_file:`samples/sensor/bme280/rpi_pico_spi_pio.overlay` +demonstrates using PIO SPI with the sensor wired to arbitrary GPIO pins. +Build and flash with: + +.. zephyr-app-commands:: + :zephyr-app: samples/sensor/bme280 + :goals: build flash + :board: rpi_pico + :gen-args: -DDTC_OVERLAY_FILE=rpi_pico_spi_pio.overlay + +Note that miso-gpios, mosi-gpios, and clk-gpios need to be assigned to the +selected PIO device in pinctrl, while cs-gpios should not; chip select is +controlled by the SPI context and must operate as a conventional GPIO pin, +not under control of PIO. + +Board-specific overlays +======================= + +If your board's devicetree does not have a BME280 node already, you can create +a board-specific devicetree overlay adding one in the :file:`boards` directory. +See existing overlays for examples. + +The build system uses these overlays by default when targeting those boards, so +no ``DTC_OVERLAY_FILE`` setting is needed when building and running. + +For example, to build for the :ref:`adafruit_feather_m0_basic_proto` using the +:zephyr_file:`samples/sensor/bme280/boards/adafruit_feather_m0_basic_proto.overlay` +overlay provided with this sample: + +.. zephyr-app-commands:: + :zephyr-app: samples/sensor/bme280 + :goals: build flash + :board: adafruit_feather_m0_basic_proto + +Sample Output +============= + +The sample prints output to the serial console. BME280 device driver messages +are also logged. Refer to your board's documentation for information on +connecting to its serial console. + +Here is example output for the default application settings, assuming that only +one BME280 sensor is connected to the standard Arduino I2C pins: + +.. code-block:: none + + [00:00:00.379,760] BME280.bme280_init: initializing "BME280_SPI" on bus "SPI_3" + [00:00:00.379,821] BME280.bme280_init: bad chip id 0xff + [00:00:00.379,821] BME280.bme280_init: initializing "BME280_I2C" on bus "I2C_0" + [00:00:00.380,340] BME280.bme280_init: ID OK + [00:00:00.385,559] BME280.bme280_init: BME280_I2C OK + *** Booting Zephyr OS build zephyr-v2.4.0-2940-gbb732ada394f *** + Found device BME280_I2C, getting sensor data + temp: 20.260000; press: 99.789019; humidity: 46.458984 + temp: 20.260000; press: 99.789480; humidity: 46.424804 + temp: 20.250000; press: 99.789246; humidity: 46.423828 + +Here is example output for the default application settings, assuming that two +different BME280 sensors are connected to the standard Arduino I2C and SPI pins: + +.. code-block:: none + + [00:00:00.377,777] BME280.bme280_init: initializing "BME280_SPI" on bus "SPI_3" + [00:00:00.377,838] BME280.bme280_init: ID OK + [00:00:00.379,608] BME280.bme280_init: BME280_SPI OK + [00:00:00.379,638] BME280.bme280_init: initializing "BME280_I2C" on bus "I2C_0" + [00:00:00.380,126] BME280.bme280_init: ID OK + [00:00:00.385,345] BME280.bme280_init: BME280_I2C OK + *** Booting Zephyr OS build zephyr-v2.4.0-2940-gbb732ada394f *** + Found device BME280_I2C, getting sensor data + temp: 20.150000; press: 99.857675; humidity: 46.447265 + temp: 20.150000; press: 99.859121; humidity: 46.458984 + temp: 20.150000; press: 99.859234; humidity: 46.469726 + +That the driver logs include a line saying ``BME280_I2C OK`` in both cases, but +``BME280_SPI OK`` is missing when that device is not connected. diff --git a/samples/sensor/ade9153a_shield/prj.conf b/samples/sensor/ade9153a_shield/prj.conf new file mode 100644 index 0000000000000..7c6994e816f6d --- /dev/null +++ b/samples/sensor/ade9153a_shield/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ADE9153A_TRIGGER=y +CONFIG_GPIO=y diff --git a/samples/sensor/ade9153a_shield/sample.yaml b/samples/sensor/ade9153a_shield/sample.yaml new file mode 100644 index 0000000000000..2e84cd1af03e7 --- /dev/null +++ b/samples/sensor/ade9153a_shield/sample.yaml @@ -0,0 +1,40 @@ +sample: + name: BME280 Sensor sample +tests: + sample.sensor.bme280: + harness: console + tags: sensors + platform_allow: + - adafruit_feather_m0_basic_proto + - rpi_pico + integration_platforms: + - adafruit_feather_m0_basic_proto + harness_config: + type: one_line + regex: + - "temp: (.*); press: (.*); humidity: (.*)" + fixture: fixture_i2c_bme280 + sample.sensor.bme280.spi: + harness: console + tags: sensors + depends_on: + - spi + - bme280 + extra_args: "DTC_OVERLAY_FILE=arduino_spi.overlay" + harness_config: + type: one_line + regex: + - "temp: (.*); press: (.*); humidity: (.*)" + fixture: fixture_spi_bme280 + sample.sensor.bme280.rpi_pico.pio: + harness: console + tags: sensors + platform_allow: rpi_pico + integration_platforms: + - rpi_pico + extra_args: "DTC_OVERLAY_FILE=rpi_pico_spi_pio.overlay" + harness_config: + type: one_line + regex: + - "temp: (.*); press: (.*); humidity: (.*)" + fixture: fixture_rpi_pico_pio_spi_bme280 diff --git a/samples/sensor/ade9153a_shield/src/main.c b/samples/sensor/ade9153a_shield/src/main.c new file mode 100644 index 0000000000000..e31ded7164a4b --- /dev/null +++ b/samples/sensor/ade9153a_shield/src/main.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025 Plentify (Pty) Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(ade9153a_sample, CONFIG_SENSOR_LOG_LEVEL); + +static const struct device *get_ade9153a_device(void) +{ + const struct device *const dev = DEVICE_DT_GET_ANY(adi_ade9153a); + + if (dev == NULL) { + LOG_ERR("\nError: no device found."); + + return NULL; + } + + if (!device_is_ready(dev)) { + LOG_ERR("\nError: Device \"%s\" is not ready; " + "check the driver initialization logs for errors.", + dev->name); + + return NULL; + } + + LOG_DBG("Found device \"%s\", getting sensor data", dev->name); + + return dev; +} + +void ade9153a_trigger_handler(const struct device *dev, const struct sensor_trigger *trigger) +{ + if (trigger->type == (int16_t)SENSOR_TRIG_ADE9153A_IRQ) { + LOG_DBG("Triggered IRQ"); + } else if (trigger->type == (int16_t)SENSOR_TRIG_ADE9153A_CF) { + LOG_DBG("Triggered CF"); + } else { + LOG_DBG("Invalid trigger type %d", trigger->type); + } +} + +static void get_reg(const struct device *dev, uint16_t addr, uint16_t size) +{ + union ade9153a_register reg = {.addr = addr, .size = size}; + + sensor_attr_get(dev, SENSOR_CHAN_ALL, (int16_t)SENSOR_ATTR_ADE9153A_REGISTER, + ®.as_sensor_value); + + LOG_DBG(" == REG%d[0x%X]:0x%X", reg.size * 8, reg.addr, reg.as_sensor_value.val1); +} + +static inline void set_reg(const struct device *dev, uint16_t addr, uint16_t size, void *data) +{ + union ade9153a_register reg = {.addr = addr, .size = size}; + + if (size == sizeof(uint16_t)) { + reg.as_sensor_value.val1 = *(uint16_t *)data; + } else { + reg.as_sensor_value.val1 = *(int32_t *)data; + } + + sensor_attr_set(dev, SENSOR_CHAN_ALL, (int16_t)SENSOR_ATTR_ADE9153A_REGISTER, + ®.as_sensor_value); + + LOG_DBG(" >> REG%d[0x%X]:0x%X", reg.size * 8, reg.addr, reg.value); + + LOG_DBG("Checking the last command:"); + get_reg(dev, ADE9153A_REG_LAST_CMD, sizeof(uint16_t)); + + if (size == sizeof(uint16_t)) { + get_reg(dev, ADE9153A_REG_LAST_DATA_16, sizeof(uint16_t)); + } else { + get_reg(dev, ADE9153A_REG_LAST_DATA_32, sizeof(uint32_t)); + } + LOG_DBG("--------------------------"); +} + +#if defined(CONFIG_ADE9153A_TRIGGER) + +static const struct sensor_trigger cf_trigger = {.chan = SENSOR_CHAN_ALL, + .type = (int16_t)SENSOR_TRIG_ADE9153A_CF}; + +static const struct sensor_trigger irq_trigger = {.chan = SENSOR_CHAN_ALL, + .type = (int16_t)SENSOR_TRIG_ADE9153A_IRQ}; + +#endif /* CONFIG_ADE9153A_TRIGGER */ + +int main(void) +{ + const struct device *dev = get_ade9153a_device(); + + if (dev == NULL) { + LOG_ERR("Error %d", -ENODEV); + return 0; + } + +#if defined(CONFIG_ADE9153A_TRIGGER) + sensor_trigger_set(dev, &cf_trigger, ade9153a_trigger_handler); + sensor_trigger_set(dev, &irq_trigger, ade9153a_trigger_handler); +#endif /* CONFIG_ADE9153A_TRIGGER */ + + while (1) { + sensor_sample_fetch_chan(dev, SENSOR_CHAN_DIE_TEMP); + + struct sensor_value sval; + + LOG_DBG("------------------------------"); + sensor_channel_get(dev, SENSOR_CHAN_DIE_TEMP, &sval); + LOG_DBG("Temperature: %d.%06d C", sval.val1, sval.val2); + + sensor_sample_fetch_chan(dev, SENSOR_CHAN_AC_CURRENT_RMS); + + sensor_channel_get(dev, SENSOR_CHAN_AC_VOLTAGE_RMS, &sval); + LOG_DBG("Voltage: %d.%06d mV", sval.val1, sval.val2); + + sensor_channel_get(dev, SENSOR_CHAN_AC_CURRENT_RMS, &sval); + LOG_DBG("Current: %d.%06d mA", sval.val1, sval.val2); + + k_sleep(K_MSEC(1000)); + } + + return 0; +}