diff --git a/samples/boards/nxp/mimxrt595_evk/system_off/sample.yaml b/samples/boards/nxp/mimxrt595_evk/system_off/sample.yaml index 5d85a280a3e43..bddbe25817daf 100644 --- a/samples/boards/nxp/mimxrt595_evk/system_off/sample.yaml +++ b/samples/boards/nxp/mimxrt595_evk/system_off/sample.yaml @@ -7,7 +7,23 @@ sample: name: Deep Power Down State Sample for mimxrt595_evk common: tags: power + harness: power + harness_config: + fixture: pm_probe + pytest_dut_scope: session + power_measurements: + elements_to_trim: 100 + min_peak_distance: 40 + min_peak_height: 0.008 + peak_padding: 40 + measurement_duration: 20 + num_of_transitions: 6 + expected_rms_values: [] + tolerance_percentage: 20 + record: + regex: + - "not used" + as_json: ['metrics'] tests: sample.boards.mimxrt595_evk.system_off: - build_only: true platform_allow: mimxrt595_evk/mimxrt595s/cm33 diff --git a/samples/drivers/adc/adc_power_shield/CMakeLists.txt b/samples/drivers/adc/adc_power_shield/CMakeLists.txt new file mode 100644 index 0000000000000..044d736330262 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ADC) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/adc/adc_power_shield/Kconfig b/samples/drivers/adc/adc_power_shield/Kconfig new file mode 100644 index 0000000000000..0ea42a502bbfb --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/Kconfig @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Centro de Inovacao EDGE. +# SPDX-License-Identifier: Apache-2.0 + +config SEQUENCE_SAMPLES + int "Number of samples to be made on the sequence for each channel." + default 5 + +config SEQUENCE_RESOLUTION + int "Set the resolution of the sequence readings." + default 12 + +config SEQUENCE_32BITS_REGISTERS + bool "ADC data sequences are on 32bits" + +source "Kconfig.zephyr" diff --git a/samples/drivers/adc/adc_power_shield/README.rst b/samples/drivers/adc/adc_power_shield/README.rst new file mode 100644 index 0000000000000..3ca9f45aa260c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/README.rst @@ -0,0 +1,80 @@ +.. zephyr:code-sample:: adc_power_shield + :name: Analog-to-Digital Converter (ADC) power shield sample + :relevant-api: adc_interface + + Read analog inputs from ADC channels, using a sequence. + +Overview +******** + +This sample is to enable general power shield for platfroms that have ADC support. + + +Building and Running +******************** + +Make sure that the ADC is enabled (``status = "okay";``) and has each channel as a +child node, with your desired settings like gain, reference, or acquisition time and +oversampling setting (if used). It is also needed to provide an alias ``adc0`` for the +desired adc. See :zephyr_file:`boards/nrf52840dk_nrf52840.overlay +` for an example of +such setup. + +Building and Running for Nordic nRF52840 +======================================== + +The sample can be built and executed for the +:zephyr:board:`nrf52840dk` as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/adc/adc_sequence + :board: nrf52840dk/nrf52840 + :goals: build flash + :compact: + +To build for another board, change "nrf52840dk/nrf52840" above to that board's name +and provide a corresponding devicetree overlay. + +Sample output +============= + +the output as below, repeated every time you input any char in the console: + +.. code-block:: console + + ADC sequence reading [1]: + - ADC_0, channel 0, 5 sequence samples: + - - 36 = 65mV + - - 35 = 63mV + - - 36 = 65mV + - - 35 = 63mV + - - 36 = 65mV + - ADC_0, channel 1, 5 sequence samples: + - - 0 = 0mV + - - 0 = 0mV + - - 1 = 1mV + - - 0 = 0mV + - - 1 = 1mV + +.. note:: If the ADC is not supported, the output will be an error message. + +You should get similar output as below, if you input a return: + +.. code-block:: console + + ==== start of adc features === + CHANNEL_COUNT: 4 + Resolution: 12 + channel_id 0 features: + - is single mode + - verf is 3300 mv + channel_id 3 features: + - is single mode + - verf is 3300 mv + channel_id 4 features: + - is single mode + - verf is 3300 mv + channel_id 7 features: + - is single mode + - verf is 3300 mv + ==== end of adc features === diff --git a/samples/drivers/adc/adc_power_shield/boards/arduino_due.overlay b/samples/drivers/adc/adc_power_shield/boards/arduino_due.overlay new file mode 100644 index 0000000000000..a56293aad299c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/arduino_due.overlay @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + pinctrl-0 = <&adc0_default>; + pinctrl-names = "default"; + + prescaler = <9>; + startup-time = <64>; + settling-time = <3>; + tracking-time = <2>; + + /* External ADC(+) */ + channel@6 { // Connector A1 + reg = <6>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,acquisition-time = ; + zephyr,input-positive = <6>; + zephyr,vref-mv = <3300>; + }; + + channel@7 { // Connector A0 + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,acquisition-time = ; + zephyr,input-positive = <7>; + zephyr,vref-mv = <3300>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/bl54l15_dvk_nrf54l15_cpuapp.overlay b/samples/drivers/adc/adc_power_shield/boards/bl54l15_dvk_nrf54l15_cpuapp.overlay new file mode 100644 index 0000000000000..e798e92876a8e --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/bl54l15_dvk_nrf54l15_cpuapp.overlay @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Nordic Semiconductor ASA + * Copyright (c) 2025 Ezurio LLC + */ + +/ { + zephyr,user { + io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 7>; + }; +}; + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.11 */ + zephyr,resolution = <10>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.06 */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* 0.9 V internal */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.13 */ + zephyr,input-negative = ; /* P1.14 */ + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/bl54l15u_dvk_nrf54l15_cpuapp.overlay b/samples/drivers/adc/adc_power_shield/boards/bl54l15u_dvk_nrf54l15_cpuapp.overlay new file mode 100644 index 0000000000000..e798e92876a8e --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/bl54l15u_dvk_nrf54l15_cpuapp.overlay @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Nordic Semiconductor ASA + * Copyright (c) 2025 Ezurio LLC + */ + +/ { + zephyr,user { + io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 7>; + }; +}; + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.11 */ + zephyr,resolution = <10>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.06 */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* 0.9 V internal */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.13 */ + zephyr,input-negative = ; /* P1.14 */ + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/cy8cproto_062_4343w.conf b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_062_4343w.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_062_4343w.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/cy8cproto_062_4343w.overlay b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_062_4343w.overlay new file mode 100644 index 0000000000000..b91102738df56 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_062_4343w.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,acquisition-time = ; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,input-positive = <0>; /* P10.0 */ + }; + + channel@1 { + reg = <1>; + zephyr,acquisition-time = ; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,input-positive = <1>; /* P10.1 */ + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/cy8cproto_063_ble.conf b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_063_ble.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_063_ble.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/cy8cproto_063_ble.overlay b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_063_ble.overlay new file mode 100644 index 0000000000000..429739fcce0a8 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/cy8cproto_063_ble.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,acquisition-time = ; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,input-positive = <2>; /* P10.2 */ + }; + + channel@1 { + reg = <1>; + zephyr,acquisition-time = ; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,input-positive = <3>; /* P10.3 */ + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/frdm_k64f.conf b/samples/drivers/adc/adc_power_shield/boards/frdm_k64f.conf new file mode 100644 index 0000000000000..3201f7a9cc405 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/frdm_k64f.conf @@ -0,0 +1 @@ +CONFIG_ADC_MCUX_ADC16_VREF_ALTERNATE=y diff --git a/samples/drivers/adc/adc_power_shield/boards/frdm_k64f.overlay b/samples/drivers/adc/adc_power_shield/boards/frdm_k64f.overlay new file mode 100644 index 0000000000000..53feed8288aef --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/frdm_k64f.overlay @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2024 NXP + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,resolution = <16>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc242.conf b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc242.conf new file mode 100644 index 0000000000000..3201f7a9cc405 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc242.conf @@ -0,0 +1 @@ +CONFIG_ADC_MCUX_ADC16_VREF_ALTERNATE=y diff --git a/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc242.overlay b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc242.overlay new file mode 100644 index 0000000000000..10f3dcbefe0cf --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc242.overlay @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2024 NXP + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc444.conf b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc444.conf new file mode 100644 index 0000000000000..3201f7a9cc405 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc444.conf @@ -0,0 +1 @@ +CONFIG_ADC_MCUX_ADC16_VREF_ALTERNATE=y diff --git a/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc444.overlay b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc444.overlay new file mode 100644 index 0000000000000..a0a08004ab2a4 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/frdm_mcxc444.overlay @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2024-2025 NXP + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&pinctrl { + pinmux_adc0: pinmux_adc0 { + group0 { + pinmux = , , + , ; + drive-strength = "low"; + slew-rate = "slow"; + }; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + /* + * To use this sample: + * - Connect ADC0 SE0 signal to voltage between 0~3.3V (J4 pin 1) + */ + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@3 { + reg = <0x3>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@4 { + reg = <0x4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@7 { + reg = <0x7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/lp_em_cc2340r5.conf b/samples/drivers/adc/adc_power_shield/boards/lp_em_cc2340r5.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/lp_em_cc2340r5.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/lp_em_cc2340r5.overlay b/samples/drivers/adc/adc_power_shield/boards/lp_em_cc2340r5.overlay new file mode 100644 index 0000000000000..74da2693655cb --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/lp_em_cc2340r5.overlay @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 BayLibre, SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,acquisition-time = ; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,acquisition-time = ; + }; + + channel@4 { + reg = <4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,acquisition-time = ; + }; + + channel@8 { + reg = <8>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_VDD_1"; + zephyr,acquisition-time = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/lpcxpresso55s69_cpu0.conf b/samples/drivers/adc/adc_power_shield/boards/lpcxpresso55s69_cpu0.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/lpcxpresso55s69_cpu0.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/lpcxpresso55s69_cpu0.overlay b/samples/drivers/adc/adc_power_shield/boards/lpcxpresso55s69_cpu0.overlay new file mode 100644 index 0000000000000..bdedee26af2b8 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/lpcxpresso55s69_cpu0.overlay @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + /* + * To use this sample: + * - Connect VREFN_TARGET to GND, and VREFP_TARGET to 3v3 + * (Resistors J8 and J9, should be populated by default) + * LPADC0 CH0A and CH0B are set up in differential mode + * - Connect LPADC0 CH0A signal to voltage between 0~3.3V (P19 pin 4) + * - Connect LPADC0 CH0B signal to voltage between 0~3.3V (P19 pin 2) + * LPADC0 CH4A is set up in single ended mode + * - Connect LPADC0 CH4A signal to voltage between 0~3.3V (P17 pin 19) + * LPADC0 CH4B is set up in single ended mode + * - Connect LPADC0 CH4B signal to voltage between 0~3.3V (P18 pin 1) + */ + + /* + * Channel 0 is used for differential mode, with 13 bit resolution + * CH0A (plus side) is routed to P19 pin 4 + * CH0B (minus side) is routed to P19 pin 2 + */ + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,input-negative = ; + }; + + /* + * Channel 1 is used in single ended mode, with 16 bit resolution + * CH4A is routed to P17 pin 19 + */ + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + }; + + /* + * Channel 2 is used in single ended mode, with 12 bit resolution + * CH4B is routed to P18 pin 1 + */ +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/mec15xxevb_assy6853.conf b/samples/drivers/adc/adc_power_shield/boards/mec15xxevb_assy6853.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mec15xxevb_assy6853.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/mec15xxevb_assy6853.overlay b/samples/drivers/adc/adc_power_shield/boards/mec15xxevb_assy6853.overlay new file mode 100644 index 0000000000000..3bf503121e3a3 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mec15xxevb_assy6853.overlay @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@4 { + reg = <4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = <0>; + }; + + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = <0>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/mec172xevb_assy6906.conf b/samples/drivers/adc/adc_power_shield/boards/mec172xevb_assy6906.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mec172xevb_assy6906.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/mec172xevb_assy6906.overlay b/samples/drivers/adc/adc_power_shield/boards/mec172xevb_assy6906.overlay new file mode 100644 index 0000000000000..a1ca287b812c9 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mec172xevb_assy6906.overlay @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = <0>; + }; + + channel@3 { + reg = <3>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = <0>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/mimxrt1040_evk.conf b/samples/drivers/adc/adc_power_shield/boards/mimxrt1040_evk.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mimxrt1040_evk.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/mimxrt1040_evk.overlay b/samples/drivers/adc/adc_power_shield/boards/mimxrt1040_evk.overlay new file mode 100644 index 0000000000000..509473ec918ac --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mimxrt1040_evk.overlay @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc1; + }; +}; + +&adc1 { + #address-cells = <1>; + #size-cells = <0>; + + /* + * To use this sample connect + * J33.1 (ADC1 CH3) and J33.2 (ADC1 CH4) to voltages between 0 and 3.3V + */ + channel@3 { + reg = <3>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; + + channel@4 { + reg = <4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/mimxrt595_evk_cm33.conf b/samples/drivers/adc/adc_power_shield/boards/mimxrt595_evk_cm33.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mimxrt595_evk_cm33.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/mimxrt595_evk_cm33.overlay b/samples/drivers/adc/adc_power_shield/boards/mimxrt595_evk_cm33.overlay new file mode 100644 index 0000000000000..3504612868856 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mimxrt595_evk_cm33.overlay @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &lpadc0; + }; +}; + +&lpadc0 { + #address-cells = <1>; + #size-cells = <0>; + + /* + * To use this sample: + * LPADC0 CH0A and CH0B are set up in differential mode (B-A) + * - Connect LPADC0 CH0A signal to voltage between 0~1.8V (J30 pin 1) + * - Connect LPADC0 CH0B signal to voltage between 0~1.8V (J30 pin 2) + * LPADC0 CH2A is set up in single ended mode + * - Connect LPADC0 CH2A signal to voltage between 0~1.8V (J30 pin 3) + */ + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,vref-mv = <1800>; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,input-negative = ; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,vref-mv = <1800>; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/mr_canhubk3.conf b/samples/drivers/adc/adc_power_shield/boards/mr_canhubk3.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mr_canhubk3.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/mr_canhubk3.overlay b/samples/drivers/adc/adc_power_shield/boards/mr_canhubk3.overlay new file mode 100644 index 0000000000000..c2fd0e11ca282 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/mr_canhubk3.overlay @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &adc2; + }; +}; + +&adc2 { + group-channel = "precision"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@3 { + reg = <3>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; + + channel@4 { + reg = <4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/nrf52840dk_nrf52840.conf b/samples/drivers/adc/adc_power_shield/boards/nrf52840dk_nrf52840.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/nrf52840dk_nrf52840.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/nrf52840dk_nrf52840.overlay b/samples/drivers/adc/adc_power_shield/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000000000..99a0e7089ca7f --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P0.03 */ + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/samples/drivers/adc/adc_power_shield/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 0000000000000..d4f9e8db7c2b4 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Nordic Semiconductor ASA + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_2"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.01 */ + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1_2"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.02 */ + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1_2"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P9.01 */ + zephyr,vref-mv = <3686>; /* 3.6V * 1024 */ + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1_2"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.03 */ + zephyr,input-negative = ; /* P1.07 */ + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/samples/drivers/adc/adc_power_shield/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 0000000000000..87b8e373f1dbc --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Nordic Semiconductor ASA + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.11 */ + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.06 */ + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* 0.9 V internal */ + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.13 */ + zephyr,input-negative = ; /* P1.14 */ + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay b/samples/drivers/adc/adc_power_shield/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay new file mode 100644 index 0000000000000..a83d64e864e55 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.06 */ + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.30 */ + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* 0.9 V internal */ + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.04 */ + zephyr,input-negative = ; /* P1.03 */ + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/nucleo_c031c6.conf b/samples/drivers/adc/adc_power_shield/boards/nucleo_c031c6.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/nucleo_c031c6.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/nucleo_c031c6.overlay b/samples/drivers/adc/adc_power_shield/boards/nucleo_c031c6.overlay new file mode 100644 index 0000000000000..937de101ee174 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/nucleo_c031c6.overlay @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc1; + }; +}; + +&adc1 { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/ophelia4ev_nrf54l15_cpuapp.overlay b/samples/drivers/adc/adc_power_shield/boards/ophelia4ev_nrf54l15_cpuapp.overlay new file mode 100644 index 0000000000000..7fedbe52ad2fc --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/ophelia4ev_nrf54l15_cpuapp.overlay @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Nordic Semiconductor ASA + */ + +/ { + zephyr,user { + io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 7>; + }; +}; + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.11 */ + zephyr,resolution = <10>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.06 */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* 0.9 V internal */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.13 */ + zephyr,input-negative = ; /* P1.14 */ + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/raytac_an54l15q_db_nrf54l15_cpuapp.overlay b/samples/drivers/adc/adc_power_shield/boards/raytac_an54l15q_db_nrf54l15_cpuapp.overlay new file mode 100644 index 0000000000000..7fedbe52ad2fc --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/raytac_an54l15q_db_nrf54l15_cpuapp.overlay @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Nordic Semiconductor ASA + */ + +/ { + zephyr,user { + io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 7>; + }; +}; + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.11 */ + zephyr,resolution = <10>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.06 */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* 0.9 V internal */ + zephyr,resolution = <12>; + zephyr,oversampling = <8>; + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; /* P1.13 */ + zephyr,input-negative = ; /* P1.14 */ + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/rsk_rx130_512kb.overlay b/samples/drivers/adc/adc_power_shield/boards/rsk_rx130_512kb.overlay new file mode 100644 index 0000000000000..0d5f1936e6376 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rsk_rx130_512kb.overlay @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + status = "okay"; + pinctrl-0 = <&adc_default>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/rza3ul_smarc.conf b/samples/drivers/adc/adc_power_shield/boards/rza3ul_smarc.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rza3ul_smarc.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/rza3ul_smarc.overlay b/samples/drivers/adc/adc_power_shield/boards/rza3ul_smarc.overlay new file mode 100644 index 0000000000000..7646d429ce7ae --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rza3ul_smarc.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/rzg3s_smarc_r9a08g045s33gbg_cm33.conf b/samples/drivers/adc/adc_power_shield/boards/rzg3s_smarc_r9a08g045s33gbg_cm33.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzg3s_smarc_r9a08g045s33gbg_cm33.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/rzg3s_smarc_r9a08g045s33gbg_cm33.overlay b/samples/drivers/adc/adc_power_shield/boards/rzg3s_smarc_r9a08g045s33gbg_cm33.overlay new file mode 100644 index 0000000000000..1a650b366faf8 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzg3s_smarc_r9a08g045s33gbg_cm33.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/rzn2l_rsk.conf b/samples/drivers/adc/adc_power_shield/boards/rzn2l_rsk.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzn2l_rsk.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/rzn2l_rsk.overlay b/samples/drivers/adc/adc_power_shield/boards/rzn2l_rsk.overlay new file mode 100644 index 0000000000000..af12380e170a3 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzn2l_rsk.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/rzt2m_rsk_r9a07g075m24gbg_cr520.conf b/samples/drivers/adc/adc_power_shield/boards/rzt2m_rsk_r9a07g075m24gbg_cr520.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzt2m_rsk_r9a07g075m24gbg_cr520.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/rzt2m_rsk_r9a07g075m24gbg_cr520.overlay b/samples/drivers/adc/adc_power_shield/boards/rzt2m_rsk_r9a07g075m24gbg_cr520.overlay new file mode 100644 index 0000000000000..af12380e170a3 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzt2m_rsk_r9a07g075m24gbg_cr520.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/rzv2l_smarc_r9a07g054l23gbg_cm33.conf b/samples/drivers/adc/adc_power_shield/boards/rzv2l_smarc_r9a07g054l23gbg_cm33.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzv2l_smarc_r9a07g054l23gbg_cm33.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/rzv2l_smarc_r9a07g054l23gbg_cm33.overlay b/samples/drivers/adc/adc_power_shield/boards/rzv2l_smarc_r9a07g054l23gbg_cm33.overlay new file mode 100644 index 0000000000000..18e4fbb8700d1 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/rzv2l_smarc_r9a07g054l23gbg_cm33.overlay @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc; + }; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; + + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,resolution = <12>; + zephyr,acquisition-time = ; + zephyr,vref-mv = <1800>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/s32k148_evb.overlay b/samples/drivers/adc/adc_power_shield/boards/s32k148_evb.overlay new file mode 100644 index 0000000000000..834b1d0292776 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/s32k148_evb.overlay @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Accenture + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&pinctrl { + adc0_default: adc0_default { + group_1 { + pinmux = ; + drive-strength = "low"; + }; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + pinctrl-0 = <&adc0_default>; + pinctrl-names = "default"; + + channel@1c { + reg = <28>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu0.conf b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu0.conf new file mode 100644 index 0000000000000..7db6cee82cd77 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu0.conf @@ -0,0 +1,4 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu0.overlay b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu0.overlay new file mode 100644 index 0000000000000..0bcd43f5a8073 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu0.overlay @@ -0,0 +1,56 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &sar_adc0; + }; + + zephyr,user { + io-channels = <&sar_adc0 3>, <&sar_adc0 4>, <&sar_adc0 5>, <&sar_adc0 6>; + }; +}; + +&sar_adc0 { + group-channel = "standard"; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@3 { + reg = <3>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@4 { + reg = <4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@6 { + reg = <6>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu1.conf b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu1.conf new file mode 100644 index 0000000000000..7db6cee82cd77 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu1.conf @@ -0,0 +1,4 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu1.overlay b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu1.overlay new file mode 100644 index 0000000000000..5b848e0c5af72 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/s32z2xxdc2_s32z270_rtu1.overlay @@ -0,0 +1,7 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "s32z2xxdc2_s32z270_rtu0.overlay" diff --git a/samples/drivers/adc/adc_power_shield/boards/sam4s_xplained.conf b/samples/drivers/adc/adc_power_shield/boards/sam4s_xplained.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam4s_xplained.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/sam4s_xplained.overlay b/samples/drivers/adc/adc_power_shield/boards/sam4s_xplained.overlay new file mode 100644 index 0000000000000..5eedbb6fc65c9 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam4s_xplained.overlay @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + /* External ADC(+) */ + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,acquisition-time = ; + zephyr,input-positive = <5>; + }; + + /* Internal temperature sensor */ + channel@f { + reg = <15>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,acquisition-time = ; + zephyr,input-positive = <15>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/sam_e70_xplained.conf b/samples/drivers/adc/adc_power_shield/boards/sam_e70_xplained.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam_e70_xplained.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/sam_e70_xplained.overlay b/samples/drivers/adc/adc_power_shield/boards/sam_e70_xplained.overlay new file mode 100644 index 0000000000000..3da0d7d4c8383 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam_e70_xplained.overlay @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &afec0; + }; +}; + +&afec0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; + + channel@8 { + reg = <8>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21.conf b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21.overlay b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21.overlay new file mode 100644 index 0000000000000..19b3d3104d7b6 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21.overlay @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &afec0; + }; +}; + +&afec0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + }; + + channel@8 { + reg = <8>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_EXTERNAL0"; + zephyr,vref-mv = <3300>; + zephyr,acquisition-time = ; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21b.conf b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21b.conf new file mode 100644 index 0000000000000..65f176fd23b9c --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21b.conf @@ -0,0 +1 @@ +CONFIG_SEQUENCE_RESOLUTION=12 diff --git a/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21b.overlay b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21b.overlay new file mode 100644 index 0000000000000..190e8c95b0dc5 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/sam_v71_xult_samv71q21b.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sam_v71_xult_samv71q21.overlay" diff --git a/samples/drivers/adc/adc_power_shield/boards/stm32f030_demo.conf b/samples/drivers/adc/adc_power_shield/boards/stm32f030_demo.conf new file mode 100644 index 0000000000000..cd05ed9949e52 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/stm32f030_demo.conf @@ -0,0 +1,20 @@ +# A4114 driver can only acquire one sequence sample +CONFIG_SEQUENCE_SAMPLES=1 +CONFIG_SEQUENCE_RESOLUTION=12 +CONFIG_SEQUENCE_32BITS_REGISTERS=y +CONFIG_LOG=n +#CONFIG_SCHED_SIMPLE=y +#CONFIG_WAITQ_SIMPLE=y +#CONFIG_ARM_MPU=n +CONFIG_GEN_SW_ISR_TABLE=n +CONFIG_GEN_ISR_TABLES=n +CONFIG_CBPRINTF_NANO=y + +# Memory constraints +CONFIG_MAIN_STACK_SIZE=512 +CONFIG_IDLE_STACK_SIZE=256 +CONFIG_ISR_STACK_SIZE=512 +CONFIG_HEAP_MEM_POOL_SIZE=0 + +# ADC minimal +CONFIG_ADC_ASYNC=n diff --git a/samples/drivers/adc/adc_power_shield/boards/stm32f030_demo.overlay b/samples/drivers/adc/adc_power_shield/boards/stm32f030_demo.overlay new file mode 100644 index 0000000000000..4125611e836fc --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/stm32f030_demo.overlay @@ -0,0 +1,110 @@ +/* + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + adc0 = &adc1; + }; +}; + +&clk_hse { + status = "disabled"; +}; + +&clk_hsi { + clock-frequency = ; + status = "okay"; +}; + +&pll { + prediv = <1>; + mul = <12>; + clocks = <&clk_hsi>; + status = "okay"; +}; + +&adc1 { + #address-cells = <1>; + #size-cells = <0>; + pinctrl-0 = <&adc_in0_pa0 &adc_in1_pa1 &adc_in2_pa2 &adc_in3_pa3 + &adc_in4_pa4 &adc_in5_pa5 &adc_in6_pa6 &adc_in7_pa7>; + pinctrl-names = "default"; + st,adc-clock-source = "SYNC"; + st,adc-prescaler = <4>; + status = "okay"; + + channel@0 { + reg = <0x0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@2 { + reg = <0x2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@3 { + reg = <3>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@4 { + reg = <0x4>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@5 { + reg = <5>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@6 { + reg = <0x6>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + channel@7 { + reg = <7>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,vref-mv = <1250>; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/samples/drivers/adc/adc_power_shield/boards/ucans32k1sic.overlay b/samples/drivers/adc/adc_power_shield/boards/ucans32k1sic.overlay new file mode 100644 index 0000000000000..b1d6b62fff239 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/boards/ucans32k1sic.overlay @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Accenture + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + aliases { + adc0 = &adc0; + }; +}; + +&pinctrl { + adc0_default: adc0_default { + group_1 { + pinmux = ; + drive-strength = "low"; + }; + }; +}; + +&adc0 { + #address-cells = <1>; + #size-cells = <0>; + + channel@c { + reg = <12>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + pinctrl-0 = <&adc0_default>; + pinctrl-names = "default"; +}; diff --git a/samples/drivers/adc/adc_power_shield/prj.conf b/samples/drivers/adc/adc_power_shield/prj.conf new file mode 100644 index 0000000000000..ed49b2fbadfe7 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/prj.conf @@ -0,0 +1,6 @@ +CONFIG_ADC=y +CONFIG_UART_CONSOLE=y +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_GETCHAR=y +CONFIG_CONSOLE_GETCHAR_BUFSIZE=64 +CONFIG_CONSOLE_PUTCHAR_BUFSIZE=512 diff --git a/samples/drivers/adc/adc_power_shield/sample.yaml b/samples/drivers/adc/adc_power_shield/sample.yaml new file mode 100644 index 0000000000000..b48360a4b9ab0 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/sample.yaml @@ -0,0 +1,28 @@ +sample: + name: ADC power shield sample +common: + tags: + - adc + depends_on: adc + filter: dt_alias_exists("adc0") + harness: console + timeout: 10 + harness_config: + type: multi_line + regex: + - "ADC sequence reading \\[\\d+\\]:" + - "- .+, channel \\d+, \\d+ sequence samples:" + - "- - \\d+ (= \\d+mV)|(\\(value in mV not available\\))" +tests: + sample.drivers.adc.adc_power_shield: + integration_platforms: + - nrf52840dk/nrf52840 + sample.drivers.adc.adc_power_shield.8bit: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + - nrf54lm20dk/nrf54lm20a/cpuapp + integration_platforms: + - nrf54l15dk/nrf54l15/cpuapp + extra_configs: + - CONFIG_SEQUENCE_RESOLUTION=8 diff --git a/samples/drivers/adc/adc_power_shield/src/main.c b/samples/drivers/adc/adc_power_shield/src/main.c new file mode 100644 index 0000000000000..0b6d120a775c4 --- /dev/null +++ b/samples/drivers/adc/adc_power_shield/src/main.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024 Centro de Inovacao EDGE + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +/* ADC node from the devicetree. */ +#define ADC_NODE DT_ALIAS(adc0) + +/* Auxiliary macro to obtain channel vref, if available. */ +#define CHANNEL_VREF(node_id) DT_PROP_OR(node_id, zephyr_vref_mv, 0) + +/* Data of ADC device specified in devicetree. */ +static const struct device *adc = DEVICE_DT_GET(ADC_NODE); + +/* Data array of ADC channels for the specified ADC. */ +static const struct adc_channel_cfg channel_cfgs[] = { + DT_FOREACH_CHILD_SEP(ADC_NODE, ADC_CHANNEL_CFG_DT, (,))}; + +/* Data array of ADC channel voltage references. */ +static uint32_t vrefs_mv[] = {DT_FOREACH_CHILD_SEP(ADC_NODE, CHANNEL_VREF, (,))}; + +/* Get the number of channels defined on the DTS. */ +#define CHANNEL_COUNT ARRAY_SIZE(channel_cfgs) + +int main(void) +{ + int err; + uint32_t count = 0; +#ifdef CONFIG_SEQUENCE_32BITS_REGISTERS + uint32_t channel_reading[CONFIG_SEQUENCE_SAMPLES][CHANNEL_COUNT]; +#else + uint16_t channel_reading[CONFIG_SEQUENCE_SAMPLES][CHANNEL_COUNT]; +#endif + + /* Options for the sequence sampling. */ + const struct adc_sequence_options options = { + .extra_samplings = CONFIG_SEQUENCE_SAMPLES - 1, + .interval_us = 0, + }; + + /* Configure the sampling sequence to be made. */ + struct adc_sequence sequence = { + .buffer = channel_reading, + /* buffer size in bytes, not number of samples */ + .buffer_size = sizeof(channel_reading), + .resolution = CONFIG_SEQUENCE_RESOLUTION, + .options = &options, + }; + + console_init(); + + if (!device_is_ready(adc)) { + printf("ADC controller device %s not ready\n", adc->name); + return 0; + } + + /* Configure channels individually prior to sampling. */ + for (size_t i = 0U; i < CHANNEL_COUNT; i++) { + sequence.channels |= BIT(channel_cfgs[i].channel_id); + err = adc_channel_setup(adc, &channel_cfgs[i]); + if (err < 0) { + printf("Could not setup channel #%d (%d)\n", i, err); + return 0; + } + if ((vrefs_mv[i] == 0) && (channel_cfgs[i].reference == ADC_REF_INTERNAL)) { + vrefs_mv[i] = adc_ref_internal(adc); + } + } + + printf("\r\nadc power shield sample starts\n"); + + while (1) { + uint8_t c = console_getchar(); + + k_msleep(100); + + /* print capacity when input is '\r'*/ + if (c == '\r') { + console_putchar('\n'); + printf("==== start of adc features ===\n"); + printf("CHANNEL_COUNT: %d\n", CHANNEL_COUNT); + printf("Resolution: %d\n", CONFIG_SEQUENCE_RESOLUTION); + for (size_t channel_index = 0U; channel_index < CHANNEL_COUNT; + channel_index++) { + printf("\tchannel_id %d features:\n", + channel_cfgs[channel_index].channel_id); + if (channel_cfgs[channel_index].differential) { + printf("\t - is diff mode\n"); + } else { + printf("\t - is single mode\n"); + } + printf("\t - verf is %d mv\n", vrefs_mv[channel_index]); + } + printf("==== end of adc features ===\n"); + continue; + } + + printf("ADC sequence reading [%u]:\n", count++); + + err = adc_read(adc, &sequence); + if (err < 0) { + printf("Could not read (%d)\n", err); + continue; + } + + for (size_t channel_index = 0U; channel_index < CHANNEL_COUNT; channel_index++) { + int32_t val_mv; + + printf("- %s, channel %" PRId32 ", %" PRId32 + " sequence samples (diff = %d):\n", + adc->name, channel_cfgs[channel_index].channel_id, + CONFIG_SEQUENCE_SAMPLES, channel_cfgs[channel_index].differential); + for (size_t sample_index = 0U; sample_index < CONFIG_SEQUENCE_SAMPLES; + sample_index++) { + + val_mv = channel_reading[sample_index][channel_index]; + + printf("- - %" PRId32, val_mv); + err = adc_raw_to_millivolts(vrefs_mv[channel_index], + channel_cfgs[channel_index].gain, + CONFIG_SEQUENCE_RESOLUTION, &val_mv); + + /* conversion to mV may not be supported, skip if not */ + if ((err < 0) || vrefs_mv[channel_index] == 0) { + printf(" (value in mV not available)\n"); + } else { + printf(" = %" PRId32 "mV\n", val_mv); + } + } + } + printf("==== end of reading ===\n"); + } + + return 0; +} diff --git a/scripts/pylib/power-twister-harness/README.rst b/scripts/pylib/power-twister-harness/README.rst new file mode 100644 index 0000000000000..5712974978502 --- /dev/null +++ b/scripts/pylib/power-twister-harness/README.rst @@ -0,0 +1,471 @@ +Power Twister Harness +===================== + +A Python testing framework for measuring and validating power consumption in Zephyr RTOS applications using hardware power monitoring probes. + +Overview +-------- + +The Power Twister Harness is designed to automate power consumption testing for embedded devices running Zephyr RTOS. It integrates with hardware power monitoring solutions to measure RMS (Root Mean Square) current values and validate them against expected thresholds with configurable tolerance levels. + +Features +-------- + +* **Hardware Power Monitor Integration**: Supports various power monitoring probes through an abstract interface +* **Multi-Platform Support**: STM Power Shield, General ADC platforms, and extensible architecture +* **RMS Current Measurement**: Calculates RMS current values from raw power data +* **Automated Validation**: Compares measured values against expected thresholds with tolerance checking +* **Peak Detection**: Identifies power consumption transitions and peaks in the measurement data +* **Flexible Configuration**: YAML-based configuration system with environment variable support +* **Real-time Measurements**: Configurable sample rates and measurement durations +* **Comprehensive Logging**: Detailed logging for debugging and result recording + +Requirements +------------ + +* Python 3.8+ +* pytest >= 6.0 +* PyYAML >= 5.4 +* numpy >= 1.20 (for advanced data processing) +* scipy >= 1.7 (for signal processing and peak detection) +* matplotlib >= 3.3 (optional, for plotting) +* Zephyr RTOS testing environment +* Compatible power monitoring hardware + +Installation +------------ + +1. Ensure you have the Zephyr development environment set up +2. Install required Python dependencies:: + + pip install pytest PyYAML numpy scipy matplotlib + +3. Set up your power monitoring hardware according to the manufacturer's instructions + +Supported Hardware Platforms +----------------------------- + +STM Power Shield +~~~~~~~~~~~~~~~~ + +The default power monitoring solution using STM-based hardware. + +* High precision current measurements +* Multiple channel support +* USB/Serial interface +* Real-time data streaming + +General ADC Platform +~~~~~~~~~~~~~~~~~~~~ + +A flexible, extensible ADC-based power monitoring solution that works with various hardware platforms and development boards. + +**Key Features:** + +* **Multi-channel ADC support**: Up to 8 configurable measurement channels +* **YAML-based configuration**: Comprehensive configuration through power_shield.yaml +* **Hardware abstraction**: Compatible with various ADC implementations (SPI, I2C, UART) +* **Advanced calibration**: Built-in scaling, offset correction, and temperature compensation +* **Flexible sampling**: Configurable sample rates from 1Hz to 100kHz +* **Data buffering**: Circular buffer support for continuous measurements +* **Protocol support**: Multiple communication protocols (UART, SPI, I2C, USB) + +**Supported ADC Types:** + +* External ADC modules (ADS1115, ADS1256, MCP3208, etc.) +* Built-in MCU ADCs (STM32, Nordic nRF, ESP32, etc.) +* Custom ADC implementations via serial protocol +* USB-based ADC devices + +**Hardware Connection Examples:** + +For current sensing with shunt resistor:: + + VDD_SOURCE ──[Rsense]── VDD_TARGET + │ │ + │ └── ADC_CH_NEG (or GND for single-ended) + └─────────── ADC_CH_POS + │ + Host PC ──[Serial/USB]── Target Device with ADC + +For voltage monitoring:: + + VDD_TARGET ──[Voltage Divider]── ADC_CH_INPUT + │ + Host PC ──[Serial/USB]────── Target Device with ADC + +See ``general_adc_platform/README.md`` for detailed hardware setup and implementation guide. + +Configuration +------------- + +Environment Variables +~~~~~~~~~~~~~~~~~~~~~ + +* ``PROBE_CLASS``: Specifies the power monitor class to use + + - ``stm_powershield`` (default): STM Power Shield + - ``general_adc_platform.GeneralAdcPowerMonitor.GeneralPowerShield``: General ADC platform + +* ``PROBE_SETTING_PATH``: Directory containing probe configuration files (default: current directory) +* ``POWER_SHIELD_CONFIG``: Override default config file name (default: power_shield.yaml) +* ``ADC_DEBUG_MODE``: Enable debug mode for ADC platform (0/1, default: 0) + +Configuration Files +~~~~~~~~~~~~~~~~~~~ + +**power_shield.yaml Example:** + +:: + + # Power Shield Configuration + power_shield: + # Hardware settings + hardware: + platform: "general_adc" + adc_type: "ads1115" + communication: + protocol: "uart" + port: "/dev/ttyUSB0" + baudrate: 115200 + timeout: 5.0 + + # Channel configuration + channels: + - id: 0 + name: "VDD_CORE" + enabled: true + shunt_resistor: 0.1 # Ohms + gain: 1 + offset: 0.0 + calibration_factor: 1.0 + + - id: 1 + name: "VDD_IO" + enabled: true + shunt_resistor: 0.1 + gain: 1 + offset: 0.0 + calibration_factor: 1.0 + + # Measurement settings + measurement: + sampling_rate: 1000 # Hz + buffer_size: 10000 + averaging: 1 + resolution: 16 # bits + reference_voltage: 3.3 # V + + # Processing settings + processing: + apply_filtering: true + filter_type: "lowpass" + filter_cutoff: 100 # Hz + remove_dc_offset: true + calibration_enabled: true + +**Configuration Validation:** + +The General ADC platform includes comprehensive configuration validation: + +* Channel conflict detection +* Hardware capability checking +* Parameter range validation +* Communication protocol verification + +Test Data Structure +~~~~~~~~~~~~~~~~~~~ + +Enhanced test data structure with additional parameters for ST power shield:: + + { + # Basic measurement parameters + 'measurement_duration': 10, # Duration of measurement in seconds + 'elements_to_trim': 100, # Initial samples to exclude + 'sampling_rate': 1000, # ADC sampling rate in Hz + + # Peak detection and analysis + 'num_of_transitions': 5, # Expected number of power state transitions + 'min_peak_distance': 50, # Minimum samples between peaks + 'min_peak_height': 0.001, # Minimum peak height in Amps (1mA) + 'peak_padding': 10, # Samples to exclude around peaks + 'peak_detection_algorithm': 'scipy', # 'scipy', 'custom', 'threshold' + + # Expected values and validation + 'expected_rms_values': [1.5, 2.0], # Expected RMS current values in mA + 'tolerance_percentage': 10, # Measurement tolerance (±10%) + 'absolute_tolerance': 0.1, # Absolute tolerance in mA + + # Multi-channel configuration + 'active_channels': [0, 1], # List of channels to measure + 'channel_weights': [1.0, 1.0], # Weighting factors for channels + 'synchronize_channels': true, # Synchronize multi-channel sampling + + # Advanced analysis + 'calculate_power': false, # Calculate power consumption + 'frequency_analysis': false, # Perform FFT analysis + 'harmonic_analysis': false, # Analyze harmonics + 'statistical_analysis': true, # Calculate statistics + + # Data processing + 'apply_filtering': true, # Apply digital filtering + 'remove_outliers': true, # Remove statistical outliers + 'outlier_threshold': 3.0, # Standard deviations for outlier detection + + # Reporting + 'generate_plots': false, # Generate measurement plots + 'export_raw_data': false, # Export raw measurement data + 'detailed_logging': true # Enable detailed logging + } + +Usage +----- + +Basic Test Execution +~~~~~~~~~~~~~~~~~~~~ + +Using STM Power Shield (default):: + + pytest test_power.py + +Using General ADC Platform:: + + PROBE_CLASS=general_powershield pytest test_power.py + +With Custom Configuration:: + + PROBE_SETTING_PATH=/path/to/config PROBE_CLASS=general_powershield pytest test_power.py + +Advanced Usage Examples:: + + # Enable debug mode + ADC_DEBUG_MODE=1 PROBE_CLASS=general_powershield pytest test_power.py -v + + # Use custom config file + POWER_SHIELD_CONFIG=my_adc_config.yaml PROBE_CLASS=general_powershield pytest test_power.py + + # Generate detailed reports + pytest test_power.py --html=power_report.html --self-contained-html + +Outputs +~~~~~~~ + +A "power_shield" folder will be created in the "build_dir" of DUT application + +including:: + + handler.log + _current_data_.csv + _voltage_data_.csv + _power_data_.csv + +If the csv can successful generated, we judge this as pass criteria. User can analyze +the power number futher. + +Hardware Setup Examples +~~~~~~~~~~~~~~~~~~~~~~~ + +**STM Power Shield Setup:** + +:: + + Target Device ──[USB]── STM Power Shield ──[USB]── Host PC + +**General ADC Platform - UART Setup:** + +:: + + Target MCU with ADC ──[UART]── Host PC + │ + Current Sense Circuit: + VDD ──[Rsense]── Load + │ │ + │ └── ADC_CH_NEG + └────── ADC_CH_POS + +**General ADC Platform - External ADC Setup:** + +:: + + Host PC ──[USB/Serial]── MCU ──[SPI/I2C]── External ADC (ADS1115) + │ + Current Sense ─────────┘ + VDD ──[Rsense]── Load + +**General ADC Platform - Multi-Channel Setup:** + +:: + + Host PC ──[USB]── Development Board + │ + ┌────┴────┐ + │ ADC │ + │ CH0-CH3 │ + └─────────┘ + │ + ┌─────────────────────────────────────┐ + │ CH0: VDD_CORE ──[R1]── Load1 │ + │ CH1: VDD_IO ──[R2]── Load2 │ + │ CH2: VDD_RF ──[R3]── Load3 │ + │ CH3: VDD_PERIPH──[R4]── Load4 │ + └─────────────────────────────────────┘ + +Test Function +~~~~~~~~~~~~~ + +The enhanced test function ``test_power_harness`` performs: + +1. **Hardware Detection**: Auto-detect connected ADC hardware +2. **Configuration Loading**: Load and validate YAML configuration +3. **Multi-Channel Setup**: Configure multiple measurement channels +4. **Calibration**: Apply calibration coefficients and corrections +5. **Synchronized Measurement**: Perform synchronized multi-channel sampling +6. **Advanced Processing**: Apply filtering, peak detection, and analysis +7. **Statistical Validation**: Compare against expected values with tolerance +8. **Comprehensive Reporting**: Generate detailed reports and logs + +Troubleshooting +--------------- + +Common Issues +~~~~~~~~~~~~~ + +**Hardware Connection Issues:** + +* Verify USB/Serial connections are secure +* Check device permissions (Linux/macOS may require udev rules) +* Ensure correct COM port or device path in configuration +* Verify power supply to ADC hardware + +**Configuration Problems:** + +* Validate YAML syntax using online YAML validators +* Check file paths and permissions for configuration files +* Verify channel IDs don't conflict +* Ensure sampling rates are within hardware limits + +**Measurement Accuracy:** + +* Calibrate shunt resistors with precision multimeter +* Check for ground loops and noise sources +* Verify ADC reference voltage stability +* Use appropriate filtering for noisy environments + +**Performance Issues:** + +* Reduce sampling rate for long measurements +* Increase buffer sizes for high-speed sampling +* Use appropriate USB cables for high-speed data transfer +* Monitor system resources during measurements + +Debug Mode +~~~~~~~~~~ + +Enable debug mode for detailed troubleshooting:: + + ADC_DEBUG_MODE=1 pytest test_power.py -v -s + +Debug output includes: + +* Hardware detection results +* Configuration validation details +* Real-time measurement data +* Communication protocol debugging +* Error stack traces with context + +Logging Configuration +~~~~~~~~~~~~~~~~~~~~~ + +Configure logging levels in your test environment:: + + import logging + logging.basicConfig(level=logging.DEBUG) + +Log levels available: + +* ``DEBUG``: Detailed diagnostic information +* ``INFO``: General operational messages +* ``WARNING``: Warning messages for potential issues +* ``ERROR``: Error messages for failures +* ``CRITICAL``: Critical errors that stop execution + +Contributing +------------ + +Development Setup +~~~~~~~~~~~~~~~~~ + +1. Clone the Zephyr repository +2. Set up development environment:: + + cd zephyr/scripts/pylib/power-twister-harness + pip install -e . + pip install -r requirements-dev.txt + +3. Run tests:: + + pytest tests/ -v + +Code Style +~~~~~~~~~~ + +* Follow PEP 8 coding standards +* Use type hints for function signatures +* Add docstrings for all public methods +* Include unit tests for new features + +Submitting Changes +~~~~~~~~~~~~~~~~~~ + +1. Create feature branch from main +2. Implement changes with tests +3. Update documentation as needed +4. Submit pull request with detailed description + +License +------- + +This project is licensed under the Apache License 2.0. See the LICENSE file in the Zephyr project root for details. + +Support +------- + +* **Documentation**: https://docs.zephyrproject.org/ +* **Issues**: Report bugs and feature requests on GitHub +* **Community**: Join the Zephyr Discord or mailing lists +* **Commercial Support**: Contact Zephyr project maintainers + +Changelog +--------- + +Version 2.0.0 +~~~~~~~~~~~~~~ + +* Added General ADC Platform support +* Multi-channel measurement capabilities +* Enhanced configuration system with YAML validation +* Advanced signal processing and filtering +* Improved error handling and debugging +* Comprehensive test coverage + +Version 1.0.0 +~~~~~~~~~~~~~~ + +* Initial release with STM Power Shield support +* Basic RMS current measurement +* Simple peak detection +* pytest integration + +API Reference +------------- + +Abstract PowerMonitor Interface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All power monitor implementations must provide: + +* ``connect(dft: DeviceAdapter) -> bool``: Connect to monitoring hardware +* ``init(device_id: str = None) -> bool``: Initialize the power monitor +* ``measure(duration: int) -> None``: Start measurement for specified duration +* ``get_data(duration: int) -> list[float]``: Retrieve measurement data +* ``close() -> None``: Close connection and cleanup resources diff --git a/scripts/pylib/power-twister-harness/conftest.py b/scripts/pylib/power-twister-harness/conftest.py index 01c880043e5c3..58f92d60968b7 100644 --- a/scripts/pylib/power-twister-harness/conftest.py +++ b/scripts/pylib/power-twister-harness/conftest.py @@ -1,9 +1,16 @@ # Copyright: (c) 2025, Intel Corporation +# Copyright 2025 NXP import json import logging +import os +from collections.abc import Generator import pytest from twister_harness import DeviceAdapter +from twister_harness.device.factory import DeviceFactory +from twister_harness.exceptions import TwisterHarnessException +from twister_harness.twister_harness_config import DeviceConfig +from utils.UartSettings import UARTParser, UARTSettings def pytest_addoption(parser): @@ -11,8 +18,9 @@ def pytest_addoption(parser): @pytest.fixture -def probe_class(request, probe_path): +def probe_class(request, probe_path, dft): path = probe_path # Get path of power device + probe = None if request.param == 'stm_powershield': from stm32l562e_dk.PowerShield import PowerShield @@ -20,14 +28,22 @@ def probe_class(request, probe_path): probe.connect(path) probe.init() + if request.param == 'general_powershield': + from general_adc_platform.GeneralAdcPowerMonitor import GeneralPowerShield + + probe = GeneralPowerShield() # Instantiate the power monitor probe + probe.connect(dft) + probe.init() + yield probe - if request.param == 'stm_powershield': + if request.param in ['stm_powershield', 'general_powershield']: probe.disconnect() @pytest.fixture(name='probe_path', scope='session') def fixture_probe_path(request, dut: DeviceAdapter): + probe_port = None for fixture in dut.device_config.fixtures: if fixture.startswith('pm_probe'): probe_port = fixture.split(':')[1] @@ -59,3 +75,74 @@ def fixture_test_data(request): pytest.fail(f"Missing required test data key: {key}") return measurements_dict + + +def determine_scope(fixture_name, config): + """Determine fixture scope based on command line options.""" + if dut_scope := config.getoption("--dut-scope", None): + return dut_scope + return 'function' + + +@pytest.fixture(scope='session') +def dft_object( + request: pytest.FixtureRequest, probe_path: str, dut: DeviceAdapter +) -> Generator[DeviceAdapter, None, None]: + """Return device object - without run application.""" + if probe_path: + parser = UARTParser() + settings: UARTSettings = parser.parse(probe_path) + logging.info(f"uart settings {settings}") + build_dir = dut.device_config.build_dir / "power_shield" + if not os.path.exists(build_dir): + os.mkdir(build_dir, 0o755) + if settings: + device_config: DeviceConfig = DeviceConfig( + type="hardware", + build_dir=build_dir, + base_timeout=dut.device_config.base_timeout, + flash_timeout=dut.device_config.flash_timeout, + platform="genernal_adc_platform", + serial=settings.port, + baud=settings.baudrate, + runner="", + runner_params="", + id="", + product="General", + serial_pty=None, + flash_before="", + west_flash_extra_args="", + pre_script="", + post_script="", + post_flash_script="", + fixtures="", + extra_test_args="", + ) + device_class: DeviceAdapter = DeviceFactory.get_device(device_config.type) + device_object = device_class(device_config) + try: + yield device_object + finally: # to make sure we close all running processes execution + device_object.close() + + +@pytest.fixture(scope=determine_scope) +def dft(request: pytest.FixtureRequest, dft_object) -> Generator[DeviceAdapter, None, None]: + """Return launched HardwareAdapter device - with hardware device flashed and connected.""" + # Get twister harness config from pytest request + # Initialize log files with test node name + dft_object.initialize_log_files(request.node.name) + try: + # Launch the hardware adapter (flash and connect) + try: + dft_object.launch() + logging.info( + 'DFT: HardwareAdapter launched for device %s', dft_object.device_config.serial + ) + except TwisterHarnessException as e: + logging.warning(f'TwisterFlashException ignored during launch: {e}') + yield dft_object + finally: + # Ensure proper cleanup + dft_object.close() + logging.info('DFT: HardwareAdapter closed') diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/GeneralAdcPowerMonitor.py b/scripts/pylib/power-twister-harness/general_adc_platform/GeneralAdcPowerMonitor.py new file mode 100644 index 0000000000000..5f1d55c1e906f --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/GeneralAdcPowerMonitor.py @@ -0,0 +1,851 @@ +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +import csv +import json +import logging +import os +import re +import sys +import time +from datetime import datetime + +import yaml + +try: + # Primary import attempt: import PowerMonitor from abstract module + from abstract.PowerMonitor import PowerMonitor +except ImportError: + # Fallback import strategy: explicitly add parent directory to sys.path + # and retry the import. This handles cases where the module path resolution + # fails due to different execution contexts or Python path configurations + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + from abstract.PowerMonitor import PowerMonitor + +try: + # Import DeviceAdapter from twister_harness for device communication + from twister_harness import DeviceAdapter +except ImportError: + # Fallback: create a dummy DeviceAdapter class if twister_harness is not available + # This allows the module to be imported and tested in environments where + # twister_harness is not installed + class DeviceAdapter: + """A simple dummy class""" + + def __init__(self): + pass + + def init(self): + """dummy init""" + + def __str__(self): + """String representation of the object.""" + return "DummyClass" + + +from general_adc_platform.ProbeCap import ProbeCap +from general_adc_platform.ProbeMeasure import ADCDevice, ADCReadingsData, ChannelReadings +from general_adc_platform.ProbeSettings import ProbeSettings + + +def normalize_name(name): + ''' + normalize name for path + ''' + return name.replace('/', '_') + + +class GeneralPowerShield(PowerMonitor): + """ + A simple implementation of PowerMonitor for general ADC platforms. + This implementation simulates power measurements for demonstration purposes. + In a real implementation, this would interface with actual ADC hardware. + init process is: __init__-> connect -> init, see conftest.probe_class + """ + + def __init__(self): + self.device_id = 0 + self.is_initialized = False + self.sample_rate = 1000 # samples per second + self.logger = logging.getLogger(__name__) + self.dft: DeviceAdapter = None + self.probe_cap: ProbeCap = None + self.probe_settings: ProbeSettings = None + self.samples: list(ADCReadingsData) = [] + self.voltage_mv = {} + self.current_ma = {} + + def connect(self, dft: DeviceAdapter): + """Opens the connection using the SerialHandler.""" + self.dft = dft + time.sleep(0.1) + self.dft.write(b'\r') + configs = self.dft.readlines_until( + regex=r"==== end of adc features ===", + timeout=5, + print_output=True, + ) + if configs: + self.probe_cap = ProbeCap.from_dict(self._parse_adc_config_log("\n".join(configs))) + if self.probe_cap and self.probe_cap.channel_count: + self.logger.info("General ADC Power Monitor connect successfully") + return + + self.logger.info("General power shield connect failed, no valid response") + self.logger.info(f"{configs}") + + def init(self, device_id: str = None) -> bool: + """ + Initialize the power monitor with the specified device ID. + + Args: + device_id (str): Address/identifier of the power monitor device + + Returns: + bool: True if initialization successful, False otherwise + """ + try: + # Get probe settings path from environment variable + probe_setting_path = os.environ.get('PROBE_SETTING_PATH', '.') + + # Construct path to probe_settings.yaml + power_shield_config_path = os.path.join(probe_setting_path, 'probe_settings.yaml') + # Check if the config file exists + if not os.path.exists(power_shield_config_path): + self.logger.warning( + "Power shield config file not found at: %s", power_shield_config_path + ) + # Fallback: look for config file in the same directory as this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + fallback_config_path = os.path.join(script_dir, 'probe_settings.yaml') + + if os.path.exists(fallback_config_path): + power_shield_config_path = fallback_config_path + self.logger.info("Found fallback config file at: %s", fallback_config_path) + else: + self.logger.error( + "Config file not found in fallback location: %s", fallback_config_path + ) + return False + + # Read and parse the YAML configuration file + self.probe_settings = ProbeSettings.from_yaml(power_shield_config_path) + self.logger.info("Loaded power shield configuration from: %s", power_shield_config_path) + self.logger.info(f"Loaded power shield settings: {self.probe_settings}") + + # Apply configuration settings + if self.probe_settings and self.probe_settings.device_id: + self.device_id = device_id + + # Mark as initialized + self.is_initialized = True + self.logger.info( + "Power monitor initialized successfully with device ID: %s", self.device_id + ) + return True + + except (OSError, yaml.YAMLError, KeyError) as e: + self.logger.error("Failed to initialize power monitor: %s", e) + self.is_initialized = False + return False + + def disconnect(self): + """Closes the connection using the SerialHandler.""" + if self.dft: + self.dft.close() + + def measure(self, duration: int) -> None: + """ + Start a power measurement for the specified duration. + + Args: + duration (int): The duration of the measurement in seconds + + Raises: + RuntimeError: If the monitor is not initialized + """ + if not self.is_initialized: + raise RuntimeError("Power monitor not initialized. Call init() first.") + + self.logger.info("Starting power measurement for %d seconds", duration) + + # For this simulation, we just log the action + self.logger.info("Power measurement started") + + start_time = time.time() + end_time = start_time + duration + + while time.time() < end_time: + self.dft.write(b'M') + measures = self.dft.readlines_until( + regex=r"==== end of reading ===", + timeout=5, + print_output=True, + ) + + measure_data: dict = self._parse_adc_data_log("\n".join(measures)) + if measure_data: + self.samples += [ADCReadingsData.from_dict(measure_data)] + + # Optional: Add a small delay to prevent overwhelming the device + # This can be adjusted based on your hardware requirements + time.sleep(0.1) # 100ms delay between measurements + + elapsed_time = time.time() - start_time + self.logger.info("Power measurement completed. Actual duration: %.2f seconds", elapsed_time) + self.logger.debug("Total samples collected: %d", len(self.samples)) + + def get_data(self, duration: int = 0) -> list[float]: + """ + Measure current for the specified duration and return the data + in aligned with the probe caps, the date in one measure depends + on the adc_power_shield application settings CONFIG_SEQUENCE_SAMPLES, + and the measure interval is depends on adc hardware and fixed delay + (100ms) in the application. + Args: + duration (int): The numbers of measured data to use, + if 0, we will use all measured data + Returns: + List[float]: An array of measured current values in amperes + Raises: + RuntimeError: If the monitor is not initialized + """ + if not self.is_initialized: + raise RuntimeError("Power monitor not initialized. Call init() first.") + + # Extract voltage data from samples + voltage_data = self._extract_voltage_data_from_samples(self.samples) + + # Process routes and calculate current data + current_data, route_voltage = self._process_routes_and_calculate_current(voltage_data) + + # Store results + self.voltage_mv = route_voltage + self.current_ma = current_data + return current_data + + def _extract_voltage_data_from_samples(self, samples: list) -> dict[str, list[float]]: + """ + Extract voltage data from all samples for channels in use. + + Returns: + dict: Voltage data by channel + """ + voltage_data = {} # by channel + channels_in_use = self.probe_settings.channel_set if self.probe_settings else set() + + for sample in samples: + for _adc_name in sample.adcs: + adc_device: ADCDevice = sample.get_adc(_adc_name) + for _channel_name in adc_device.channels: + _ch = _channel_name.replace("channel_", "", 1) + + # Initialize channel list if not exists + if _ch not in voltage_data: + voltage_data[_ch] = [] + + adc_readings: ChannelReadings = adc_device.get_channel(_channel_name) + + # Only process channels that are in use (or all if no channel_set defined) + if not channels_in_use or int(_ch) in channels_in_use: + channel_readings: float = adc_readings.get_average_voltage() + voltage_data[_ch].append(channel_readings) + + return voltage_data + + def _process_routes_and_calculate_current( + self, voltage_data: dict[str, list[float]] + ) -> tuple[dict, dict]: + """ + Process all routes and calculate current data. + Args: + voltage_data: Voltage data by channel + + Returns: + tuple: (current_data, route_voltage) dictionaries + """ + current_data = {} + route_voltage = {} # by route name + for _route in self.probe_settings.routes: + # Validate route channels + if not self._validate_route_channels(_route, voltage_data): + continue + + # Calculate current for this route + route_current, route_volt = self._calculate_route_current(_route, voltage_data) + + current_data[_route.name] = route_current + if route_volt: # Only add if there's voltage data (non-differential mode) + route_voltage[_route.name] = route_volt + + return current_data, route_voltage + + def _validate_route_channels(self, route, voltage_data: dict[str, list[float]]) -> bool: + """ + Validate that route channels have data and matching lengths. + + Args: + route: Route object to validate + voltage_data: Voltage data by channel + + Returns: + bool: True if route is valid, False otherwise + """ + route_channels = {str(route.channels.channels_p), str(route.channels.channels_n)} + + # Check if channels exist in voltage data + if not route_channels.issubset(voltage_data.keys()): + self.logger.warning(f"channel data is not measured for {route.name} skip") + self.logger.warning(f"voltage_data keys {voltage_data.keys()} skip") + self.logger.warning(f"channels {route_channels}") + return False + + # Check if channel data lengths match + channels_p_str = str(route.channels.channels_p) + channels_n_str = str(route.channels.channels_n) + + if len(voltage_data[channels_p_str]) != len(voltage_data[channels_n_str]): + self.logger.warning(f"len mismatch {route.name}: {channels_p_str} vs {channels_n_str}") + self.logger.warning(f"channels_p : {len(voltage_data[channels_p_str])}") + self.logger.warning(f"channels_n : {len(voltage_data[channels_n_str])}") + return False + + return True + + def _calculate_route_current( + self, route, voltage_data: dict[str, list[float]] + ) -> tuple[list[float], list[float]]: + """ + Calculate current data for a specific route. + + Args: + route: Route object + voltage_data: Voltage data by channel + + Returns: + tuple: (current_data_list, voltage_data_list) + """ + current_data_list = [] + voltage_data_list = [] + + channels_p_str = str(route.channels.channels_p) + channels_n_str = str(route.channels.channels_n) + + if route.is_differential: + # In differential mode _vp equal to _vn + current_data_list = self._calculate_differential_current( + route, voltage_data[channels_p_str] + ) + else: + # Non-differential mode + current_data_list, voltage_data_list = self._calculate_non_differential_current( + route, voltage_data[channels_p_str], voltage_data[channels_n_str] + ) + + return current_data_list, voltage_data_list + + def _calculate_differential_current(self, route, voltage_p_data: list[float]) -> list[float]: + """ + Calculate current for differential mode route. + + Args: + route: Route object with calibration and conversion methods + voltage_p_data: Voltage data from positive channel + + Returns: + list: Current data in amperes + """ + current_data = [] + + for _vp in voltage_p_data: + _cal_vp = self.probe_settings.calibration.apply_calibration(_vp) + _cur_data = route.voltage_to_current(_cal_vp) + current_data.append(_cur_data) + + return current_data + + def _calculate_non_differential_current( + self, route, voltage_p_data: list[float], voltage_n_data: list[float] + ) -> tuple[list[float], list[float]]: + """ + Calculate current for non-differential mode route. + + Args: + route: Route object with calibration and conversion methods + voltage_p_data: Voltage data from positive channel + voltage_n_data: Voltage data from negative channel + + Returns: + tuple: (current_data, voltage_data) lists + """ + current_data = [] + voltage_data = [] + + for _vp, _vn in zip(voltage_p_data, voltage_n_data, strict=False): + _cal_vp = self.probe_settings.calibration.apply_calibration(_vp) + _cal_vn = self.probe_settings.calibration.apply_calibration(_vn) + _cur_data = route.voltage_to_current(abs(_cal_vp - _cal_vn)) + current_data.append(_cur_data) + voltage_data.append(_cal_vp) + + return current_data, voltage_data + + def dump_power(self, filename: str = None, fake_run: bool = False): + """ + Calculate and dump power data (P = V × I) for routes to CSV file + + Args: + filename (str, optional): Output CSV filename. If None, generates timestamp-based name + + Returns: + bool: True if successful, False otherwise + """ + + if not self.current_ma or not self.voltage_mv: + self.logger.warning("No current or voltage data available to calculate power") + return False + + try: + # Calculate power data (P = V × I) + power_mw = {} + + # Find common routes between voltage and current data + common_routes = set(self.voltage_mv.keys()) & set(self.current_ma.keys()) + + if not common_routes: + self.logger.warning("No common routes found between voltage and current data") + return False + + for route in common_routes: + voltage_samples = self.voltage_mv[route] + current_samples = self.current_ma[route] + + # Ensure both have the same number of samples + min_samples = min(len(voltage_samples), len(current_samples)) + + if min_samples == 0: + self.logger.warning("No samples available for route %s", route) + continue + + power_mw[route] = [] + for i in range(min_samples): + # Power = Voltage (mV) × Current (mA) / 1000 = Power (mW) + power = (voltage_samples[i] * current_samples[i]) / 1000.0 + power_mw[route].append(power) + + if not power_mw: + self.logger.warning("No power data calculated") + return False + + # Generate filename if not provided + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + if filename is None: + dump_name = f"unknown_platform_power_data_{timestamp}.csv" + else: + dump_name = f"{normalize_name(filename)}_power_data_{timestamp}.csv" + + # Determine the maximum number of samples across all routes + max_samples = max(len(samples) for samples in power_mw.values()) + + if fake_run: + self.logger.info(f"power_mw {power_mw}") + return True + + # Write CSV file + dump_path = os.path.join(self.dft.device_config.build_dir, dump_name) + with open(dump_path, 'w', newline='', encoding='utf-8') as csvfile: + # Get route names for headers + route_names = list(power_mw.keys()) + + # Create CSV writer with headers + writer = csv.writer(csvfile) + + # Write header row + headers = ['Sample_Index'] + [f'{route}_Power_mW' for route in route_names] + writer.writerow(headers) + + # Write data rows + for i in range(max_samples): + row = [i] # Sample index + for route in route_names: + # Add power value if available, otherwise empty + if i < len(power_mw[route]): + row.append(f"{power_mw[route][i]:.6f}") # Format to 6 decimal places + else: + row.append('') # Empty cell for missing data + writer.writerow(row) + + self.logger.info("Power data successfully dumped to %s", dump_path) + self.logger.info("Exported %s samples for %s routes", max_samples, len(route_names)) + + # Log power statistics + for route in route_names: + avg_power = sum(power_mw[route]) / len(power_mw[route]) + max_power = max(power_mw[route]) + min_power = min(power_mw[route]) + self.logger.info( + "Route %s: Avg=%.3fmW, Max=%.3fmW, Min=%.3fmW", + route, + avg_power, + max_power, + min_power, + ) + + return True + + except OSError as e: + self.logger.error("Failed to write power data to CSV: %s", e) + return False + except (ValueError, KeyError, ZeroDivisionError) as e: + self.logger.error("Error processing power data: %s", e) + return False + + def dump_current(self, filename: str = None, fake_run: bool = False): + """ + Dump current data for routes to CSV file + + Args: + filename (str, optional): Output CSV filename. If None, generates timestamp-based name + + Returns: + bool: True if successful, False otherwise + """ + + if not self.current_ma: + self.logger.warning("No current data available to dump") + return False + + try: + # Generate filename if not provided + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + if filename is None: + dump_name = f"unknown_platform_current_data_{timestamp}.csv" + else: + dump_name = f"{normalize_name(filename)}_current_data_{timestamp}.csv" + + # Determine the maximum number of samples across all routes + if self.current_ma: + max_samples = max(len(samples) for samples in self.current_ma.values()) + else: + self.logger.warning("No current samples to dump") + return False + + if fake_run: + self.logger.info(f"current_ma {self.current_ma}") + return True + + # Write CSV file + dump_path = os.path.join(self.dft.device_config.build_dir, dump_name) + with open(dump_path, 'w', newline='', encoding='utf-8') as csvfile: + # Get route names for headers + route_names = list(self.current_ma.keys()) + + # Create CSV writer with headers + writer = csv.writer(csvfile) + + # Write header row + headers = ['Sample_Index'] + [f'{route}_Current_mA' for route in route_names] + writer.writerow(headers) + + # Write data rows + for i in range(max_samples): + row = [i] # Sample index + for route in route_names: + # Add current value if available, otherwise empty + if i < len(self.current_ma[route]): + row.append(self.current_ma[route][i]) + else: + row.append('') + writer.writerow(row) + + self.logger.info(f"Current data dumped to {dump_path}") + return True + + except Exception as e: + self.logger.error(f"Failed to dump current data: {e}") + return False + + def dump_voltage(self, filename: str = None, fake_run: bool = False): + """ + Dump voltage data for routes to CSV file + + Args: + filename (str, optional): Output CSV filename. If None, generates timestamp-based name + + Returns: + bool: True if successful, False otherwise + """ + + if not self.voltage_mv: + self.logger.warning("No voltage data available to dump") + return False + + try: + # Generate filename if not provided + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + if filename is None: + dump_name = f"unknown_platform_voltage_data_{timestamp}.csv" + else: + dump_name = f"{normalize_name(filename)}_voltage_data_{timestamp}.csv" + + # Determine the maximum number of samples across all routes + if self.voltage_mv: + max_samples = max(len(samples) for samples in self.voltage_mv.values()) + else: + self.logger.warning("No voltage samples to dump") + return False + + if fake_run: + self.logger.info(f"voltage {self.voltage_mv}") + return True + + # Write CSV file + dump_path = os.path.join(self.dft.device_config.build_dir, dump_name) + with open(dump_path, 'w', newline='', encoding='utf-8') as csvfile: + # Get route names for headers + route_names = list(self.voltage_mv.keys()) + + # Create CSV writer with headers + writer = csv.writer(csvfile) + + # Write header row + headers = ['Sample_Index'] + [f'{route}_Voltage_mV' for route in route_names] + writer.writerow(headers) + + # Write data rows + for i in range(max_samples): + row = [i] # Sample index + for route in route_names: + # Add voltage value if available, otherwise empty + if i < len(self.voltage_mv[route]): + row.append(self.voltage_mv[route][i]) + else: + row.append('') # Empty cell for missing data + writer.writerow(row) + + self.logger.info(f"Voltage data successfully dumped to {dump_path}") + self.logger.info(f"Exported {max_samples} samples for {len(route_names)} routes") + return True + + except OSError as e: + self.logger.error(f"Failed to write voltage data to CSV: {e}") + return False + except Exception as e: + self.logger.error(f"Unexpected error while dumping voltage data: {e}") + return False + + def close(self) -> None: + """ + Close the power monitor connection and cleanup resources. + """ + if self.is_initialized: + self.logger.info("Closing General ADC Power Monitor") + + # In a real implementation, you would: + # - Stop any ongoing measurements + # - Close device connections + # - Free allocated resources + # - Reset hardware to default state + self.disconnect() + self.is_initialized = False + self.device_id = None + self.logger.info("General ADC Power Monitor closed") + + def get_device_info(self) -> dict: + """ + Get information about the connected device. + + Returns: + dict: Device information + """ + return self.probe_cap.to_dict() + + @staticmethod + def _parse_adc_config_log(log_text: str) -> dict: + """ + Parse ADC features log and convert to dictionary format + """ + result = {} + + # Extract channel count and resolution + channel_count_match = re.search(r'CHANNEL_COUNT:\s*(\d+)', log_text) + resolution_match = re.search(r'Resolution:\s*(\d+)', log_text) + + if channel_count_match: + result['channel_count'] = int(channel_count_match.group(1)) + + if resolution_match: + result['resolution'] = int(resolution_match.group(1)) + + # Extract channel information + result['channels'] = {} + + # Find all channel blocks + channel_pattern = r'channel_id\s+(\d+)\s+features:(.*?)(?=channel_id\s+\d+|$)' + channel_matches = re.findall(channel_pattern, log_text, re.DOTALL) + + for channel_id, features_text in channel_matches: + channel_id = int(channel_id) + result['channels'][channel_id] = {} + + # Parse features for each channel + # Extract mode + if 'is single mode' in features_text: + result['channels'][channel_id]['mode'] = 'single' + elif 'is diff mode' in features_text: + result['channels'][channel_id]['mode'] = 'diff' + + # Extract verf (voltage reference) + verf_match = re.search(r'verf is (\d+)\s*mv', features_text) + if verf_match: + result['channels'][channel_id]['verf_mv'] = int(verf_match.group(1)) + + return result + + @staticmethod + def _parse_adc_data_log(log_text: str) -> dict: + """ + Parse ADC sequence reading log into a structured dictionary. + + Args: + log_text: The raw log text containing ADC readings + + Returns: + Dictionary with parsed ADC data + """ + result = {"sequence_number": None, "adcs": {}} + + lines = log_text.strip().split('\n') + current_adc = None + current_channel = None + + for line in lines: + line = line.strip() + + # Parse sequence header + if line.startswith("ADC sequence reading"): + sequence_match = re.search(r'\[(\d+)\]', line) + if sequence_match: + result["sequence_number"] = int(sequence_match.group(1)) + + # Parse ADC and channel header - Updated to handle actual format + elif line.startswith("- adc@"): + # Handle format: "- adc@4003b000, channel 0, 5 sequence samples (differential = 0):" + adc_match = re.search( + r'- adc@([0-9a-fA-F]+),\s*channel\s+(\d+),\s*(\d+)\s+sequence samples', line + ) + if adc_match: + adc_address = adc_match.group(1) + channel_num = int(adc_match.group(2)) + sample_count = int(adc_match.group(3)) + + current_adc = f"adc@{adc_address}" + current_channel = channel_num + + if current_adc not in result["adcs"]: + result["adcs"][current_adc] = {} + + result["adcs"][current_adc][f"channel_{current_channel}"] = { + "sample_count": sample_count, + "readings": [], + } + else: + # Handle incomplete ADC line (like the one at line 635) + adc_simple_match = re.search(r'- adc@([0-9a-fA-F]+)', line) + if adc_simple_match: + adc_address = adc_simple_match.group(1) + current_adc = f"adc@{adc_address}" + # Initialize if not exists, but don't set current_channel yet + if current_adc not in result["adcs"]: + result["adcs"][current_adc] = {} + + # Parse individual readings + elif line.startswith("- - ") and current_adc and current_channel is not None: + reading_match = re.search(r'- - (\d+) = (\d+)mV', line) + if reading_match: + raw_value = int(reading_match.group(1)) + voltage_mv = int(reading_match.group(2)) + + result["adcs"][current_adc][f"channel_{current_channel}"]["readings"].append( + {"raw": raw_value, "voltage_mv": voltage_mv} + ) + + return result + + +def log_setup(): + ''' + log setup + ''' + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(logging.DEBUG) + formatter = logging.Formatter( + "%(asctime)s - %(name)s -%(filename)s : %(lineno)d - %(levelname)s - %(message)s" + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + return logger + + +if __name__ == "__main__": + log_setup() + log_text = """==== start of adc features === +CHANNEL_COUNT: 2 +Resolution: 12 +channel_id 0 features: +- is single mode +- verf is 3300 mv +channel_id 1 features: +- is single mode +- verf is 3300 mv +==== end of adc features ===""" + + parsed_data = GeneralPowerShield._parse_adc_config_log(log_text) + print(json.dumps(parsed_data, indent=2)) + log_data = """ADC sequence reading [41]: +- adc@4003b000, channel 0, 5 sequence samples (diff = 0): +- - 155 = 124mV +- - 198 = 159mV +- - 201 = 161mV +- - 198 = 159mV +- - 198 = 159mV +- adc@4003b000, channel 3, 5 sequence samples (diff = 0): +- - 4095 = 3299mV +- - 4095 = 3299mV +- - 4095 = 3299mV +- - 4095 = 3299mV +- - 4095 = 3299mV +- adc@4003b000, channel 4, 5 sequence samples (diff = 0): +- - 2159 = 1739mV +- - 2159 = 1739mV +- - 2160 = 1740mV +- - 2160 = 1740mV +- - 2158 = 1738mV +- adc@4003b000, channel 7, 5 sequence samples (diff = 0): +- - 4095 = 3299mV +- - 4095 = 3299mV +- - 4095 = 3299mV +- - 4095 = 3299mV +- - 4095 = 3299mV +==== end of reading === +""" + # Parse with detailed structure + parsed_measure = GeneralPowerShield._parse_adc_data_log(log_data) + print("Detailed parsing:") + print(parsed_measure) + print("\n" + "=" * 50 + "\n") + + _gps = GeneralPowerShield() + _gps.init() + samples = [] + samples += [ADCReadingsData.from_dict(parsed_measure)] + _gps.samples = samples + parsed_measure = _gps._extract_voltage_data_from_samples(_gps.samples) + print(f"{parsed_measure}") + _gps.get_data() + assert _gps.dump_voltage(fake_run=True) + assert _gps.dump_current(fake_run=True) + assert _gps.dump_power(fake_run=True) diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/ProbeCap.py b/scripts/pylib/power-twister-harness/general_adc_platform/ProbeCap.py new file mode 100644 index 0000000000000..50d841ea86f5c --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/ProbeCap.py @@ -0,0 +1,84 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +from dataclasses import dataclass + + +@dataclass +class ChannelConfig: + """Configuration for a single ADC channel.""" + + mode: str + verf_mv: int + + +@dataclass +class ProbeCap: + """Probe capacity configuration.""" + + channel_count: int + resolution: int + channels: dict[str, ChannelConfig] + + @classmethod + def from_dict(cls, data: dict) -> 'ProbeCap': + """Create ADCSettings instance from dictionary.""" + channels = {} + for channel_id, channel_data in data.get('channels', {}).items(): + channels[channel_id] = ChannelConfig( + mode=channel_data['mode'], verf_mv=channel_data['verf_mv'] + ) + + return cls( + channel_count=data['channel_count'], resolution=data['resolution'], channels=channels + ) + + def to_dict(self) -> dict: + """Convert ADCSettings instance to dictionary.""" + channels_dict = {} + for channel_id, channel_config in self.channels.items(): + channels_dict[channel_id] = { + 'mode': channel_config.mode, + 'verf_mv': channel_config.verf_mv, + } + + return { + 'channel_count': self.channel_count, + 'resolution': self.resolution, + 'channels': channels_dict, + } + + def get_channel(self, channel_id: str) -> ChannelConfig | None: + """Get channel configuration by ID.""" + return self.channels.get(channel_id) + + def add_channel(self, channel_id: str, mode: str, verf_mv: int) -> None: + """Add a new channel configuration.""" + self.channels[channel_id] = ChannelConfig(mode=mode, verf_mv=verf_mv) + self.channel_count = len(self.channels) + + +# Example usage +if __name__ == "__main__": + # Example JSON data + json_data = { + "channel_count": 2, + "resolution": 12, + "channels": { + "0": {"mode": "single", "verf_mv": 3300}, + "1": {"mode": "single", "verf_mv": 3300}, + }, + } + + # Create ADCSettings from JSON + probe_cap = ProbeCap.from_dict(json_data) + print(f"ADC Settings: {probe_cap}") + + # Access channel configuration + channel_0 = probe_cap.get_channel("0") + if channel_0: + print(f"Channel 0 - Mode: {channel_0.mode}, Vref: {channel_0.verf_mv}mV") + + # Convert back to dictionary + settings_dict = probe_cap.to_dict() + print(f"Back to dict: {settings_dict}") diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/ProbeMeasure.py b/scripts/pylib/power-twister-harness/general_adc_platform/ProbeMeasure.py new file mode 100644 index 0000000000000..d3afd5123424f --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/ProbeMeasure.py @@ -0,0 +1,198 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +from dataclasses import dataclass + + +@dataclass +class Reading: + """Individual ADC reading with raw value and voltage.""" + + raw: int + voltage_mv: int + + +@dataclass +class ChannelReadings: + """Readings for a single ADC channel.""" + + sample_count: int + readings: list[Reading] + + @classmethod + def from_dict(cls, data: dict) -> 'ChannelReadings': + """Create ChannelReadings instance from dictionary.""" + readings = [ + Reading(raw=reading['raw'], voltage_mv=reading['voltage_mv']) + for reading in data['readings'] + ] + + return cls(sample_count=data['sample_count'], readings=readings) + + def to_dict(self) -> dict: + """Convert ChannelReadings instance to dictionary.""" + return { + 'sample_count': self.sample_count, + 'readings': [ + {'raw': reading.raw, 'voltage_mv': reading.voltage_mv} for reading in self.readings + ], + } + + def get_average_voltage(self) -> float: + """Calculate average voltage from all readings, excluding highest and lowest values.""" + if not self.readings: + return 0.0 + if len(self.readings) <= 2: + # If we have 2 or fewer readings, return the average of all + return sum(reading.voltage_mv for reading in self.readings) / len(self.readings) + + # Get all voltage values and sort them + voltages = [reading.voltage_mv for reading in self.readings] + voltages.sort() + + # Remove the lowest and highest values + trimmed_voltages = voltages[1:-1] + + return sum(trimmed_voltages) / len(trimmed_voltages) + + def get_average_raw(self) -> float: + """Calculate average raw value from all readings, excluding highest and lowest values.""" + if not self.readings: + return 0.0 + if len(self.readings) <= 2: + # If we have 2 or fewer readings, return the average of all + return sum(reading.raw for reading in self.readings) / len(self.readings) + + # Get all raw values and sort them + raw_values = [reading.raw for reading in self.readings] + raw_values.sort() + + # Remove the lowest and highest values + trimmed_raw_values = raw_values[1:-1] + + return sum(trimmed_raw_values) / len(trimmed_raw_values) + + +@dataclass +class ADCDevice: + """Readings for a single ADC device with multiple channels.""" + + channels: dict[str, ChannelReadings] + + @classmethod + def from_dict(cls, data: dict) -> 'ADCDevice': + """Create ADCDevice instance from dictionary.""" + channels = {} + for ch_name, channel_data in data.items(): + channels[ch_name] = ChannelReadings.from_dict(channel_data) + + return cls(channels=channels) + + def to_dict(self) -> dict: + """Convert ADCDevice instance to dictionary.""" + return { + channel_name: channel_readings.to_dict() + for channel_name, channel_readings in self.channels.items() + } + + def get_channel(self, ch_name: str) -> ChannelReadings | None: + """Get channel readings by name.""" + return self.channels.get(ch_name) + + +@dataclass +class ADCReadingsData: + """Complete ADC readings data structure.""" + + sequence_number: int + adcs: dict[str, ADCDevice] + + @classmethod + def from_dict(cls, data: dict) -> 'ADCReadingsData': + """Create ADCReadingsData instance from dictionary.""" + adcs = {} + for adc_key, adc_device_data in data['adcs'].items(): + adcs[adc_key] = ADCDevice.from_dict(adc_device_data) + + return cls(sequence_number=data['sequence_number'], adcs=adcs) + + def to_dict(self) -> dict: + """Convert ADCReadingsData instance to dictionary.""" + return { + 'sequence_number': self.sequence_number, + 'adcs': {adc_name: adc_device.to_dict() for adc_name, adc_device in self.adcs.items()}, + } + + def get_adc(self, adc_name: str) -> ADCDevice | None: + """Get ADC device by name.""" + return self.adcs.get(adc_name) + + def get_channel_readings(self, adc_name: str, ch_name: str) -> ChannelReadings | None: + """Get specific channel readings from specific ADC.""" + adc = self.get_adc(adc_name) + if adc: + return adc.get_channel(ch_name) + return None + + +# Example usage and utility functions +def calculate_power_consumption( + voltage_readings: ChannelReadings, current_readings: ChannelReadings +) -> float: + """Calculate average power consumption from voltage and current readings.""" + avg_voltage = voltage_readings.get_average_voltage() / 1000.0 # Convert to volts + avg_current = current_readings.get_average_voltage() / 1000.0 # Assuming current in mA + return avg_voltage * avg_current # Power in watts + + +if __name__ == "__main__": + # Example JSON data + json_data = { + "sequence_number": 1, + "adcs": { + "ADC_0": { + "channel_0": { + "sample_count": 5, + "readings": [ + {"raw": 36, "voltage_mv": 65}, + {"raw": 35, "voltage_mv": 63}, + {"raw": 36, "voltage_mv": 65}, + {"raw": 35, "voltage_mv": 63}, + {"raw": 36, "voltage_mv": 65}, + ], + }, + "channel_1": { + "sample_count": 5, + "readings": [ + {"raw": 0, "voltage_mv": 0}, + {"raw": 0, "voltage_mv": 0}, + {"raw": 1, "voltage_mv": 1}, + {"raw": 0, "voltage_mv": 0}, + {"raw": 1, "voltage_mv": 1}, + ], + }, + } + }, + } + + # Create ADCReadingsData from JSON + adc_data = ADCReadingsData.from_dict(json_data) + print(f"Sequence Number: {adc_data.sequence_number}") + + # Access specific channel readings + channel_0_readings = adc_data.get_channel_readings("ADC_0", "channel_0") + if channel_0_readings: + print(f"Channel 0 average voltage: {channel_0_readings.get_average_voltage():.2f} mV") + print(f"Channel 0 average raw: {channel_0_readings.get_average_raw():.2f}") + + # Convert back to dictionary + data_dict = adc_data.to_dict() + print(f"Converted back to dict: {data_dict}") + + # Print all readings for debugging + for adc_name, adc_device in adc_data.adcs.items(): + print(f"\nADC: {adc_name}") + for channel_name, channel_readings in adc_device.channels.items(): + print(f" {channel_name}: {len(channel_readings.readings)} readings") + for i, reading in enumerate(channel_readings.readings): + print(f" Reading {i + 1}: Raw={reading.raw}, Voltage={reading.voltage_mv}mV") diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/ProbeSettings.py b/scripts/pylib/power-twister-harness/general_adc_platform/ProbeSettings.py new file mode 100644 index 0000000000000..8048288f9b0b9 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/ProbeSettings.py @@ -0,0 +1,277 @@ +# Copyright 2025 NXP +# SPDX-License-Identifier: Apache-2.0 + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import yaml + + +@dataclass +class ChannelPair: + """ + Configuration for ADC channel pair (positive and negative). + for differential type, set channels_p and channels_n the same + """ + + channels_p: int + channels_n: int + + def __post_init__(self): + """Validate channel pair configuration.""" + if self.channels_p < 0 or self.channels_n < 0: + raise ValueError(f"must be non-negative, got p={self.channels_p}, n={self.channels_n}") + + +@dataclass +class RouteConfig: + """Configuration for a single measurement route.""" + + id: int + name: str + shunt_resistor: float # ohms + gain: float = 1.0 + type: str = "single" + channels: ChannelPair = None + + def __post_init__(self): + """Validate route configuration after initialization.""" + if self.id < 0: + raise ValueError(f"Route ID must be non-negative, got {self.id}") + if self.shunt_resistor <= 0: + raise ValueError(f"Shunt resistor must be positive, got {self.shunt_resistor}") + if self.gain <= 0: + raise ValueError(f"Gain must be positive, got {self.gain}") + if self.type not in ["single", "diff", "differential"]: + raise ValueError(f"Type must be 'single', 'diff', or 'differential', got {self.type}") + + @property + def current_scale_factor(self) -> float: + """Calculate current scaling factor based on shunt resistor and gain.""" + return 1.0 / (self.shunt_resistor * self.gain) + + def voltage_to_current(self, voltage: float) -> float: + """Convert measured voltage to current using shunt resistor.""" + return voltage / (self.shunt_resistor * self.gain) + + @property + def is_differential(self) -> bool: + """Check if this route uses differential measurement.""" + return self.type in ["diff", "differential"] + + +@dataclass +class CalibrationConfig: + """Calibration settings for the probe.""" + + offset: float = 0.0 + scale: float = 1.0 + + def __post_init__(self): + """Validate calibration configuration.""" + if self.scale == 0: + raise ValueError("Scale factor cannot be zero") + + def apply_calibration(self, raw_value: float) -> float: + """Apply calibration to a raw measurement value.""" + return (raw_value + self.offset) * self.scale + + +@dataclass +class ProbeSettings: + """Complete probe configuration settings.""" + + device_id: str + routes: list[RouteConfig] = field(default_factory=list) + calibration: CalibrationConfig = field(default_factory=CalibrationConfig) + + def __post_init__(self): + """Validate probe settings after initialization.""" + if not self.device_id: + raise ValueError("Device ID cannot be empty") + + # Check for duplicate route IDs + route_ids = [route.id for route in self.routes] + if len(route_ids) != len(set(route_ids)): + raise ValueError("Duplicate route IDs found") + + @classmethod + def from_yaml(cls, yaml_path: Path) -> 'ProbeSettings': + """Load probe settings from YAML file.""" + with open(yaml_path) as file: + data = yaml.safe_load(file) + + return cls.from_dict(data) + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> 'ProbeSettings': + """Create ProbeSettings from dictionary.""" + # Parse routes + routes = [] + for route_data in data.get('routes', []): + # Parse channels + channels_data = route_data.get('channels', {}) + channels = ChannelPair( + channels_p=channels_data.get('channels_p', 0), + channels_n=channels_data.get('channels_n', 0), + ) + + route = RouteConfig( + id=route_data['id'], + name=route_data['name'], + shunt_resistor=route_data['shunt_resistor'], + gain=route_data.get('gain', 1.0), + type=route_data.get('type', 'single'), + channels=channels, + ) + routes.append(route) + + # Parse calibration + cal_data = data.get('calibration', {}) + calibration = CalibrationConfig(**cal_data) + + return cls(device_id=data['device_id'], routes=routes, calibration=calibration) + + def to_dict(self) -> dict[str, Any]: + """Convert ProbeSettings to dictionary.""" + return { + 'device_id': self.device_id, + 'routes': [ + { + 'id': route.id, + 'name': route.name, + 'shunt_resistor': route.shunt_resistor, + 'gain': route.gain, + 'type': route.type, + 'channels': { + 'channels_p': route.channels.channels_p, + 'channels_n': route.channels.channels_n, + }, + } + for route in self.routes + ], + 'calibration': {'offset': self.calibration.offset, 'scale': self.calibration.scale}, + } + + def to_yaml(self, yaml_path: Path) -> None: + """Save probe settings to YAML file.""" + with open(yaml_path, 'w') as file: + yaml.dump(self.to_dict(), file, default_flow_style=False, indent=2) + + def get_route_by_id(self, route_id: int) -> RouteConfig | None: + """Get route configuration by ID.""" + for route in self.routes: + if route.id == route_id: + return route + return None + + def get_route_by_name(self, name: str) -> RouteConfig | None: + """Get route configuration by name.""" + for route in self.routes: + if route.name == name: + return route + return None + + @property + def route_count(self) -> int: + """Get total number of configured routes.""" + return len(self.routes) + + @property + def route_names(self) -> list[str]: + """Get list of all route names.""" + return [route.name for route in self.routes] + + @property + def channel_set(self) -> set[int]: + """Get list of all channels""" + channels = [] + for _route in self.routes: + channels += [_route.channels.channels_p, _route.channels.channels_n] + + return set(channels) + + def validate_measurement_data(self, data: dict[int, list[float]]) -> bool: + """Validate that measurement data matches configured routes.""" + configured_ids = {route.id for route in self.routes} + data_ids = set(data.keys()) + + if not data_ids.issubset(configured_ids): + missing_configs = data_ids - configured_ids + raise ValueError(f"Measurement data contains unconfigured routes: {missing_configs}") + + return True + + +# Example usage and factory functions +def load_default_probe_settings() -> ProbeSettings: + """Load default probe settings for ADC power monitor.""" + return ProbeSettings( + device_id="adc_power_monitor_01", + routes=[ + RouteConfig( + id=0, + name="VDD_CORE", + shunt_resistor=0.1, + gain=1.0, + type="single", + channels=ChannelPair(channels_p=0, channels_n=3), + ), + RouteConfig( + id=1, + name="VDD_IO", + shunt_resistor=0.1, + gain=1.0, + type="single", + channels=ChannelPair(channels_p=4, channels_n=7), + ), + ], + calibration=CalibrationConfig(offset=0.0, scale=1.0), + ) + + +def create_probe_settings_template(output_path: Path) -> None: + """Create a template probe settings YAML file.""" + template = load_default_probe_settings() + template.to_yaml(output_path) + + +# Example usage +if __name__ == "__main__": + # Load from YAML + settings_path = Path("probe_settings.yaml") + + try: + probe_settings = ProbeSettings.from_yaml(settings_path) + print(f"Loaded settings for device: {probe_settings.device_id}") + print(f"Configured routes: {probe_settings.route_names}") + + # Access specific route + core_route = probe_settings.get_route_by_name("VDD_CORE") + if core_route: + print(f"VDD_CORE shunt resistor: {core_route.shunt_resistor} ohms") + print( + "VDD_CORE channels: P={core_route.channels.channels_p}," + + f"N={core_route.channels.channels_n}" + ) + print(f"VDD_CORE type: {core_route.type}") + print(f"VDD_CORE is differential: {core_route.is_differential}") + + # Convert voltage measurement to current + voltage_reading = 0.05 # 50mV + current = core_route.voltage_to_current(voltage_reading) + print(f"Current: {current:.3f} A") + + # Show all routes + for route in probe_settings.routes: + print( + f"Route {route.id}: {route.name} - P:{route.channels.channels_p}," + + f" N:{route.channels.channels_n} ({route.type})" + ) + + except FileNotFoundError: + print("Creating default probe settings...") + default_settings = load_default_probe_settings() + default_settings.to_yaml(settings_path) + print(f"Default settings saved to {settings_path}") diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/README.rst b/scripts/pylib/power-twister-harness/general_adc_platform/README.rst new file mode 100644 index 0000000000000..c6730b94f42d9 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/README.rst @@ -0,0 +1,340 @@ +General ADC Platform - Power Measurement Shield +=============================================== + +This directory contains configuration and documentation for the General ADC Platform power measurement system used in Zephyr's power testing harness. + +Overview +-------- + +The General ADC Platform provides a flexible power measurement solution for Zephyr applications. It uses ADC-based current sensing through shunt resistors to monitor power consumption across multiple voltage rails. + +Components +---------- + +GeneralPowerShield +~~~~~~~~~~~~~~~~~~ + +The GeneralPowerShield is a hardware platform that provides: + +- Multi-channel current measurement capability +- Configurable shunt resistor values +- Single-ended and differential measurement modes +- Programmable gain amplification +- Integration with Zephyr's power testing framework + +Key Features +~~~~~~~~~~~~ + +- **Multi-rail monitoring**: Support for monitoring multiple power rails simultaneously +- **High precision**: Low-noise ADC measurements with configurable gain +- **Flexible routing**: Configurable channel mapping for different board layouts +- **Calibration support**: Built-in offset and scale calibration +- **Real-time monitoring**: Integration with power-twister-harness for continuous monitoring + +Configuration +------------- + +probe_settings.yaml +~~~~~~~~~~~~~~~~~~~ + +The ``probe_settings.yaml`` file defines the hardware configuration for your specific setup. + +Configuration Structure +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: yaml + + device_id: "unique_device_identifier" + routes: + - id: + name: "" + shunt_resistor: + gain: + type: "" + channels: + channels_p: + channels_n: + calibration: + offset: + scale: + +Configuration Validation +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The configuration file is validated against a JSON schema (``probe_settings_schema.json``) to ensure correctness and prevent runtime errors. + +Schema Validation Rules +""""""""""""""""""""""" + +**Required Fields:** + +- ``device_id``: Must be a non-empty string +- ``routes``: Must be an array with at least one route +- ``calibration``: Must contain both offset and scale + +**Route Validation:** + +- ``id``: Integer >= 0, must be unique across routes +- ``name``: Non-empty string describing the power rail +- ``shunt_resistor``: Positive number (> 0) in ohms +- ``gain``: Positive number (> 0) for amplifier gain +- ``type``: Must be either "single" or "differential" +- ``channels.channels_p``: Integer >= 0 for positive channel +- ``channels.channels_n``: Integer >= 0 for negative channel + +**Calibration Validation:** + +- ``offset``: Any number (can be positive, negative, or zero) +- ``scale``: Positive number (> 0) for scale factor + +**Data Types:** + +- Numbers can be integers or floating-point values +- Examples: ``0.1``, ``1.0``, ``2.5``, ``100`` + Manual Validation + """"""""""""""""" + + To validate your configuration manually: + + .. code-block:: bash + + # Using the validation script + python validate_probe_settings.py probe_settings.yaml + + # Or with custom schema file + python validate_probe_settings.py probe_settings.yaml --schema custom_schema.json + + # For verbose output + python validate_probe_settings.py probe_settings.yaml --verbose + + Alternative validation using jsonschema directly: + + .. code-block:: bash + + # Using jsonschema (if available) + python -c " + import json, yaml + from jsonschema import validate + + # Load schema and config + with open('probe_settings_schema.json') as f: + schema = json.load(f) + with open('probe_settings.yaml') as f: + config = yaml.safe_load(f) + + # Validate + validate(config, schema) + print('Configuration is valid!') + " +Common Validation Errors +"""""""""""""""""""""""" + +**"is less than or equal to the minimum"** + +This error occurs when numeric values are not positive where required: + +- ``shunt_resistor`` must be > 0 (e.g., use ``0.1`` not ``0``) +- ``gain`` must be > 0 (e.g., use ``1.0`` not ``0``) +- ``scale`` must be > 0 (e.g., use ``1.0`` not ``0``) + +**"Missing required property"** + +Ensure all required fields are present: + +.. code-block:: yaml + + # Correct structure + device_id: "my_device" + routes: + - id: 0 + name: "VDD_CORE" + shunt_resistor: 0.1 + gain: 1.0 + type: "single" + channels: + channels_p: 4 + channels_n: 0 + calibration: + offset: 0.0 + scale: 1.0 + +**"Additional properties are not allowed"** + +Remove any extra fields not defined in the schema. + +Validation Best Practices +""""""""""""""""""""""""" + +1. **Use meaningful names**: Route names should clearly identify the power rail +2. **Verify hardware values**: Ensure shunt_resistor matches physical components +3. **Check channel assignments**: Avoid conflicts between routes +4. **Validate ranges**: Ensure gain and scale values are appropriate for your setup +5. **Test configuration**: Validate before deploying to avoid runtime errors + +Parameters +^^^^^^^^^^ + +Device Configuration +""""""""""""""""""""" + +- **device_id**: Unique identifier for the measurement device +- **calibration**: Global calibration settings + + - **offset**: Voltage offset correction (V) + - **scale**: Scale factor for measurements + +Route Configuration +""""""""""""""""""" + +- **id**: Numeric identifier for the measurement route (0-based) +- **name**: Human-readable name for the power rail (e.g., "VDD_CORE", "VDD_IO") +- **shunt_resistor**: Shunt resistor value in ohms (typically 0.1Ω for low-power measurements) +- **gain**: Amplifier gain setting (1.0 = unity gain) +- **type**: Measurement type + + - ``"single"``: Single-ended measurement + - ``"differential"``: Differential measurement + +- **channels**: ADC channel mapping + + - **channels_p**: Positive input channel number + - **channels_n**: Negative input channel number (for differential measurements) + +Example Configuration +^^^^^^^^^^^^^^^^^^^^^ + +The provided example monitors two power rails: + +1. **VDD_CORE** (Route 0): + + - Single-ended measurement on ADC channel 4 + - 0.1Ω shunt resistor + - Unity gain + +2. **VDD_IO** (Route 1): + + - Single-ended measurement using channels 7 (positive) and 3 (negative) + - 0.1Ω shunt resistor + - Unity gain + +Usage +----- + +1. Hardware Setup +~~~~~~~~~~~~~~~~~ + +1. Connect your target board to the GeneralPowerShield +2. Ensure proper shunt resistor values are installed +3. Verify ADC channel connections match your configuration + +2. Configuration +~~~~~~~~~~~~~~~~ + +1. Copy and modify ``probe_settings.yaml`` for your specific setup +2. Update device_id, route names, and channel mappings +3. Set appropriate shunt resistor values and gains +4. Perform calibration if needed +5. **Validate configuration** against the schema before use + +3. Integration with Power Testing +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The configuration is automatically loaded by the power-twister-harness framework: + +.. code-block:: python + + # Example usage in test scripts + from power_twister_harness import GeneralADCPlatform + + # Load configuration (automatically validates) + platform = GeneralADCPlatform("probe_settings.yaml") + + # Start measurement + platform.start_measurement() + + # Get power data + power_data = platform.get_power_readings() + +Calibration +----------- + +Offset Calibration +~~~~~~~~~~~~~~~~~~ + +Measure the ADC reading with no current flowing and set the offset to compensate. + +Scale Calibration +~~~~~~~~~~~~~~~~~ + +Apply a known current and adjust the scale factor to match expected readings. + +Calibration Procedure +~~~~~~~~~~~~~~~~~~~~~ + +1. Connect a precision current source +2. Record ADC readings at multiple current levels +3. Calculate offset and scale corrections +4. Update ``probe_settings.yaml`` with calibration values +5. **Re-validate configuration** after changes + +Troubleshooting +--------------- + +Common Issues +~~~~~~~~~~~~~ + +1. **Incorrect readings**: Check shunt resistor values and channel mappings +2. **Noisy measurements**: Verify grounding and reduce gain if necessary +3. **Offset drift**: Perform regular calibration, especially after temperature changes +4. **Channel conflicts**: Ensure each route uses unique ADC channels +5. **Configuration validation errors**: Check schema compliance and data types + +Verification Steps +~~~~~~~~~~~~~~~~~~ + +1. **Validate configuration file** against schema +2. Verify hardware connections match configuration +3. Check shunt resistor values with multimeter +4. Validate ADC channel assignments +5. Test with known current loads + +Hardware Requirements +--------------------- + +- GeneralPowerShield board +- Compatible ADC (typically 16-bit or higher resolution) +- Precision shunt resistors (0.1Ω typical) +- Target board with accessible power rails +- USB or serial connection for data acquisition + +Software Dependencies +--------------------- + +- Python 3.7+ +- Zephyr power-twister-harness framework +- ADC driver libraries +- YAML configuration parser +- JSON Schema validation library (jsonschema) + +Contributing +------------ + +When modifying configurations: + +1. **Validate against schema** before committing +2. Test thoroughly with known loads +3. Document any hardware changes +4. Update calibration values after hardware modifications +5. Validate measurements against reference equipment +6. Update schema if adding new configuration options + +Support +------- + +For issues related to: + +- **Hardware setup**: Check connections and component values +- **Configuration**: Validate YAML syntax and parameter ranges +- **Schema validation**: Check data types and required fields +- **Calibration**: Use precision reference equipment +- **Integration**: Refer to power-twister-harness documentation diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/probe_cap.json b/scripts/pylib/power-twister-harness/general_adc_platform/probe_cap.json new file mode 100644 index 0000000000000..543b36a524909 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/probe_cap.json @@ -0,0 +1,22 @@ +{ + "channel_count": 2, + "resolution": 12, + "channels": { + "0": { + "mode": "single", + "verf_mv": 3300 + }, + "3": { + "mode": "single", + "verf_mv": 3300 + }, + "4": { + "mode": "single", + "verf_mv": 3300 + }, + "7": { + "mode": "single", + "verf_mv": 3300 + } + } +} diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/probe_measure_data.json b/scripts/pylib/power-twister-harness/general_adc_platform/probe_measure_data.json new file mode 100644 index 0000000000000..5631e246997f2 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/probe_measure_data.json @@ -0,0 +1,27 @@ +{ + "sequence_number": 1, + "adcs": { + "ADC_0": { + "channel_0": { + "sample_count": 5, + "readings": [ + {"raw": 36, "voltage_mv": 65}, + {"raw": 35, "voltage_mv": 63}, + {"raw": 36, "voltage_mv": 65}, + {"raw": 35, "voltage_mv": 63}, + {"raw": 36, "voltage_mv": 65} + ] + }, + "channel_1": { + "sample_count": 5, + "readings": [ + {"raw": 0, "voltage_mv": 0}, + {"raw": 0, "voltage_mv": 0}, + {"raw": 1, "voltage_mv": 1}, + {"raw": 0, "voltage_mv": 0}, + {"raw": 1, "voltage_mv": 1} + ] + } + } + } +} \ No newline at end of file diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/probe_settings.yaml b/scripts/pylib/power-twister-harness/general_adc_platform/probe_settings.yaml new file mode 100644 index 0000000000000..b40884d3d9897 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/probe_settings.yaml @@ -0,0 +1,22 @@ +# Example probe_settings.yaml +device_id: "adc_power_monitor_01" +routes: + - id: 0 + name: "VDD_CORE" + shunt_resistor: 0.1 # ohms + gain: 1.0 + type: "single" + channels: + channels_p: 4 + channels_n: 0 + - id: 1 + name: "VDD_IO" + shunt_resistor: 0.1 # ohms + gain: 1.0 + type: "single" + channels: + channels_p: 7 + channels_n: 3 +calibration: + offset: 0.0 + scale: 1.0 diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/probe_settings_schema.json b/scripts/pylib/power-twister-harness/general_adc_platform/probe_settings_schema.json new file mode 100644 index 0000000000000..d9f65014a6623 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/probe_settings_schema.json @@ -0,0 +1,88 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "probe_settings_schema.json", + "title": "Probe Settings Schema", + "description": "Schema for validating probe_settings.yaml configuration files", + "type": "object", + "required": ["device_id", "routes", "calibration"], + "properties": { + "device_id": { + "type": "string", + "description": "Unique identifier for the ADC power monitor device", + "minLength": 1 + }, + "routes": { + "type": "array", + "description": "Array of measurement routes configuration", + "minItems": 1, + "items": { + "type": "object", + "required": ["id", "name", "shunt_resistor", "gain", "type", "channels"], + "properties": { + "id": { + "type": "integer", + "description": "Unique route identifier", + "minimum": 0 + }, + "name": { + "type": "string", + "description": "Human-readable name for the route", + "minLength": 1 + }, + "shunt_resistor": { + "type": "number", + "description": "Shunt resistor value in ohms", + "exclusiveMinimum": 0 + }, + "gain": { + "type": "number", + "description": "Amplifier gain value", + "exclusiveMinimum": 0 + }, + "type": { + "type": "string", + "description": "Measurement type", + "enum": ["single", "differential"] + }, + "channels": { + "type": "object", + "description": "ADC channel configuration", + "required": ["channels_p", "channels_n"], + "properties": { + "channels_p": { + "type": "integer", + "description": "Positive channel number", + "minimum": 0 + }, + "channels_n": { + "type": "integer", + "description": "Negative channel number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "calibration": { + "type": "object", + "description": "Calibration parameters", + "required": ["offset", "scale"], + "properties": { + "offset": { + "type": "number", + "description": "Calibration offset value" + }, + "scale": { + "type": "number", + "description": "Calibration scale factor", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/requirements.txt b/scripts/pylib/power-twister-harness/general_adc_platform/requirements.txt new file mode 100644 index 0000000000000..cf7143e5b8086 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/requirements.txt @@ -0,0 +1,2 @@ +pyyaml>=6.0 +jsonschema>=4.0.0 diff --git a/scripts/pylib/power-twister-harness/general_adc_platform/validate_probe_settings.py b/scripts/pylib/power-twister-harness/general_adc_platform/validate_probe_settings.py new file mode 100644 index 0000000000000..2d6cd7f34c352 --- /dev/null +++ b/scripts/pylib/power-twister-harness/general_adc_platform/validate_probe_settings.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +""" +Probe Settings Validator + +This script validates probe_settings.yaml files against the defined JSON schema. +""" + +import argparse +import json +import os +import sys +from pathlib import Path +from typing import Any + +# Set UTF-8 encoding for Windows compatibility +if os.name == 'nt': # Windows + import codecs + + sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict') + sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict') + +try: + import yaml + from jsonschema import Draft7Validator +except ImportError as e: + print(f"Error: Required package not found: {e}") + print("Please install required packages:") + print("pip install pyyaml jsonschema") + sys.exit(1) + + +class ProbeSettingsValidator: + """Validator for probe settings configuration files.""" + + def __init__(self, schema_path: Path): + """Initialize validator with schema file.""" + self.schema_path = schema_path + self.schema = self._load_schema() + self.validator = Draft7Validator(self.schema) + + def _load_schema(self) -> dict[str, Any]: + """Load JSON schema from file.""" + try: + with open(self.schema_path, encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + raise FileNotFoundError( + f"Schema file not found: {self.schema_path}" + ) from FileNotFoundError + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON schema: {e}") from json.JSONDecodeError + + def _load_yaml(self, yaml_path: Path) -> dict[str, Any]: + """Load YAML configuration file.""" + try: + with open(yaml_path, encoding='utf-8') as f: + return yaml.safe_load(f) + except FileNotFoundError: + raise FileNotFoundError(f"YAML file not found: {yaml_path}") from FileNotFoundError + except yaml.YAMLError as e: + raise ValueError(f"Invalid YAML file: {e}") from yaml.YAMLError + + def validate_file(self, yaml_path: Path) -> tuple[bool, list[str]]: + """ + Validate a probe settings YAML file. + + Returns: + tuple: (is_valid, list_of_errors) + """ + try: + config = self._load_yaml(yaml_path) + errors = [] + + # Validate against schema + for error in self.validator.iter_errors(config): + error_path = " -> ".join(str(p) for p in error.absolute_path) + if error_path: + error_msg = f"Path '{error_path}': {error.message}" + else: + error_msg = f"Root: {error.message}" + errors.append(error_msg) + + # Additional custom validations + custom_errors = self._custom_validations(config) + errors.extend(custom_errors) + + return len(errors) == 0, errors + + except (FileNotFoundError, ValueError) as e: + return False, [str(e)] + + def _custom_validations(self, config: dict[str, Any]) -> list[str]: + """Perform additional custom validations.""" + errors = [] + + if 'routes' in config: + # Check for duplicate route IDs + route_ids = [route.get('id') for route in config['routes']] + if len(route_ids) != len(set(route_ids)): + errors.append("Duplicate route IDs found") + + # Check for duplicate route names + route_names = [route.get('name') for route in config['routes']] + if len(route_names) != len(set(route_names)): + errors.append("Duplicate route names found") + + # Validate channel configurations + for i, route in enumerate(config['routes']): + channels = route['channels'] + if route.get('type') != 'single' and channels.get('channels_n') == channels.get( + 'channels_p' + ): + errors.append( + f"Route {i}: diff mode channels_p and channels_n should not equal" + ) + + return errors + + +def print_success(message: str): + """Print success message with cross-platform compatibility.""" + try: + print(f"[PASS] {message}") + except UnicodeEncodeError: + print(f"[PASS] {message}") + + +def print_error(message: str): + """Print error message with cross-platform compatibility.""" + try: + print(f"[FAIL] {message}") + except UnicodeEncodeError: + print(f"[FAIL] {message}") + + +def main(): + """Main function for command-line usage.""" + parser = argparse.ArgumentParser( + description="Validate probe_settings.yaml files against schema", allow_abbrev=False + ) + parser.add_argument("yaml_file", type=Path, help="Path to probe_settings.yaml file to validate") + parser.add_argument( + "--schema", + type=Path, + default=Path(__file__).parent / "probe_settings_schema.json", + help="Path to JSON schema file (default: probe_settings_schema.json in same directory)", + ) + parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output") + + args = parser.parse_args() + + try: + validator = ProbeSettingsValidator(args.schema) + is_valid, errors = validator.validate_file(args.yaml_file) + + if is_valid: + print_success(f"{args.yaml_file} is valid") + if args.verbose: + print("All validation checks passed successfully.") + return 0 + else: + print_error(f"{args.yaml_file} is invalid:") + for error in errors: + print(f" - {error}") + return 1 + + except Exception as e: + print_error(f"Validation error: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/pylib/power-twister-harness/requirements-dev.txt b/scripts/pylib/power-twister-harness/requirements-dev.txt new file mode 100644 index 0000000000000..72ad508d62a00 --- /dev/null +++ b/scripts/pylib/power-twister-harness/requirements-dev.txt @@ -0,0 +1,27 @@ +# Development dependencies for Power Twister Harness + +# Testing framework +pytest>=6.0 +pytest-html>=3.1.0 +pytest-cov>=4.0.0 +pytest-mock>=3.10.0 + +# Code quality and formatting +black>=22.0.0 +flake8>=5.0.0 +isort>=5.10.0 +mypy>=0.991 + +# Documentation +sphinx>=5.0.0 +sphinx-rtd-theme>=1.2.0 + +# Development utilities +pre-commit>=2.20.0 +tox>=4.0.0 + +# Core runtime dependencies (also needed for development) +PyYAML>=5.4 +numpy>=1.20 +scipy>=1.7 +matplotlib>=3.3 diff --git a/scripts/pylib/power-twister-harness/test_power.py b/scripts/pylib/power-twister-harness/test_power.py index def05d4ebf6c3..893f3bebcfe08 100644 --- a/scripts/pylib/power-twister-harness/test_power.py +++ b/scripts/pylib/power-twister-harness/test_power.py @@ -1,6 +1,7 @@ # Copyright: (c) 2024, Intel Corporation import logging +import os import pytest from abstract.PowerMonitor import PowerMonitor @@ -9,9 +10,13 @@ logger = logging.getLogger(__name__) +PROBE_CLASS = os.environ.get('PROBE_CLASS', 'stm_powershield') -@pytest.mark.parametrize("probe_class", ['stm_powershield'], indirect=True) -def test_power_harness(probe_class: PowerMonitor, test_data, request, dut: DeviceAdapter): + +@pytest.mark.parametrize("probe_class", [PROBE_CLASS], indirect=True) +def test_power_harness( + probe_class: PowerMonitor, test_data, request, dut: DeviceAdapter, dft: DeviceAdapter +): """ This test measures and validates the RMS current values from the power monitor and compares them against expected RMS values. @@ -21,6 +26,7 @@ def test_power_harness(probe_class: PowerMonitor, test_data, request, dut: Devic test_data -- Fixture to prepare data. request -- Request object that provides access to test configuration. dut -- The Device Under Test (DUT) that the power monitor is measuring. + dft -- the power shield handler """ # Initialize the probe with the provided path @@ -30,32 +36,43 @@ def test_power_harness(probe_class: PowerMonitor, test_data, request, dut: Devic measurements_dict = test_data # Start the measurement process with the provided duration - probe.measure(time=measurements_dict['measurement_duration']) + probe.measure(measurements_dict['measurement_duration']) # Retrieve the measured data data = probe.get_data() + if hasattr(probe, 'dump_voltage'): + assert probe.dump_voltage(dut.device_config.platform) + # Calculate the RMS current values using utility functions - rms_values_measured = current_RMS( - data, - trim=measurements_dict['elements_to_trim'], - num_peaks=measurements_dict['num_of_transitions'], - peak_distance=measurements_dict['min_peak_distance'], - peak_height=measurements_dict['min_peak_height'], - padding=measurements_dict['peak_padding'], - ) + if hasattr(probe, 'dump_current'): + assert probe.dump_current(dut.device_config.platform) + else: + rms_values_measured = current_RMS( + data, + trim=measurements_dict['elements_to_trim'], + num_peaks=measurements_dict['num_of_transitions'], + peak_distance=measurements_dict['min_peak_distance'], + peak_height=measurements_dict['min_peak_height'], + padding=measurements_dict['peak_padding'], + ) - # # Convert measured values from amps to milliamps for comparison - rms_values_in_milliamps = [value * 1e3 for value in rms_values_measured] + # Convert measured values from amps to milliamps for comparison + rms_values_in_milliamps = [value * 1e3 for value in rms_values_measured] - # # Log the calculated values in milliamps for debugging purposes - logger.debug(f"Measured RMS values in mA: {rms_values_in_milliamps}") + # # Log the calculated values in milliamps for debugging purposes + logger.debug(f"Measured RMS values in mA: {rms_values_in_milliamps}") - tuples = zip(measurements_dict['expected_rms_values'], rms_values_in_milliamps, strict=False) - for expected_rms_value, measured_rms_value in tuples: - assert is_within_tolerance( - measured_rms_value, expected_rms_value, measurements_dict['tolerance_percentage'] + tuples = zip( + measurements_dict['expected_rms_values'], rms_values_in_milliamps, strict=False ) + for expected_rms_value, measured_rms_value in tuples: + assert is_within_tolerance( + measured_rms_value, expected_rms_value, measurements_dict['tolerance_percentage'] + ) + + if hasattr(probe, 'dump_power'): + assert probe.dump_power(dut.device_config.platform) def is_within_tolerance(measured_rms_value, expected_rms_value, tolerance_percentage) -> bool: diff --git a/scripts/pylib/power-twister-harness/utils/UartSettings.py b/scripts/pylib/power-twister-harness/utils/UartSettings.py new file mode 100644 index 0000000000000..ab491d6b79bd9 --- /dev/null +++ b/scripts/pylib/power-twister-harness/utils/UartSettings.py @@ -0,0 +1,360 @@ +# Copyright 2025 NXP +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import re +import sys +from dataclasses import dataclass +from enum import Enum + + +class Parity(Enum): + """ + Enum for UART parity settings. + - NONE: No parity bit + - EVEN: Even parity + - ODD: Odd parity bit + - MARK: Mark parity bit (always set to 1) + - SPACE: Space parity bit (always set to 0) + """ + + NONE = "N" + EVEN = "E" + ODD = "O" + MARK = "M" + SPACE = "S" + + +class FlowControl(Enum): + """ + Enum for UART flow control settings. + - NONE: No flow control + - RTS_CTS: RTS/CTS hardware flow control + - XON_XOFF: Software flow control using XON/XOFF + - DSR_DTR: DSR/DTR hardware flow control + """ + + NONE = "None" + RTS_CTS = "RTS/CTS" + XON_XOFF = "XON/XOFF" + DSR_DTR = "DSR/DTR" + + +@dataclass +class UARTSettings: + """Dataclass for UART settings. + Attributes: + port (str): Serial port name (e.g., COM3, /dev/ttyUSB0) + baudrate (int): Baud rate for communication + databits (int): Number of data bits (5-8) + parity (Parity): Parity setting (NONE, EVEN, ODD, MARK, SPACE) + stopbits (int | float): Number of stop bits (1, 1.5, or 2) + flowcontrol (FlowControl): Flow control setting (NONE, RTS/CTS, XON/XOFF, DSR/DTR) + """ + + port: str + baudrate: int + databits: int + parity: Parity + stopbits: int | float + flowcontrol: FlowControl + + def __str__(self): + return f"UART({self.port}, {self.baudrate}bps, \ + {self.databits}{self.parity.value}{self.stopbits}, {self.flowcontrol.value})" + + +class UARTParser: + """ + Parser for UART configuration strings. + This class provides methods to parse a UART configuration string + and validate the port name. + """ + + # Multiple patterns to handle different formats + UART_PATTERN_COLON = re.compile( + r'^([^:,]+)(?::(\d+))?(?::([5-8]))?(?::([NEOMS]))?(?::([12](?:\.5)?))?(?::(.+))?$', + re.IGNORECASE, + ) + + # Pattern for comma-separated format like "COM3:115200,8-N-1,None" + UART_PATTERN_COMMA = re.compile( + r'^([^:,]+)(?::(\d+))?(?:,([5-8])-([NEOMS])-([12](?:\.5)?))?(?:,(.+))?$', re.IGNORECASE + ) + + # Pattern for mixed comma format like "/dev/ttyACM0,38400,8-N-1.5,None" + UART_PATTERN_MIXED = re.compile( + r'^([^:,]+)(?:[,:](\d+))?(?:[,:]([5-8])-([NEOMS])-([12](?:\.5)?))?(?:[,:](.+))?$', + re.IGNORECASE, + ) + + VALID_BAUDRATES = { + 110, + 300, + 600, + 1200, + 2400, + 4800, + 9600, + 14400, + 19200, + 38400, + 57600, + 115200, + 128000, + 256000, + 460800, + 921600, + } + + @classmethod + def parse(cls, uart_string: str) -> UARTSettings | None: + """ + Parse a UART configuration string into UARTSettings object. + Tries to parse as much as possible, using defaults for invalid values. + + Supports formats: + - "PORT" + - "PORT:BAUDRATE" + - "PORT:BAUDRATE:DATABITS:PARITY:STOPBITS:FLOWCONTROL" + - "PORT:BAUDRATE,DATABITS-PARITY-STOPBITS,FLOWCONTROL" + - "PORT,BAUDRATE,DATABITS-PARITY-STOPBITS,FLOWCONTROL" + + Args: + uart_string: UART configuration string + + Returns: + UARTSettings object with parsed or default values + + Raises: + ValueError: Only if the input is completely invalid (empty/None) + """ + if not uart_string or not isinstance(uart_string, str): + raise ValueError("Input must be a non-empty string") + + # Default values + defaults = { + 'port': 'COM1', + 'baudrate': 115200, + 'databits': 8, + 'parity': Parity.NONE, + 'stopbits': 1, + 'flowcontrol': FlowControl.NONE, + } + + uart_string = uart_string.strip() + port = None + baudrate_str = None + databits_str = None + parity_str = None + stopbits_str = None + flowcontrol_str = None + + # Try different patterns in order of specificity + patterns = [cls.UART_PATTERN_MIXED, cls.UART_PATTERN_COMMA, cls.UART_PATTERN_COLON] + + for pattern in patterns: + match = pattern.match(uart_string) + if match: + port, baudrate_str, databits_str, parity_str, stopbits_str, flowcontrol_str = ( + match.groups() + ) + break + + if not match: + # Fallback: manual parsing with flexible separators + # Replace commas with colons for uniform parsing + normalized = uart_string.replace(',', ':') + parts = normalized.split(':') + + if len(parts) < 1 or not parts[0].strip(): + raise ValueError("At least port must be specified") + + port = parts[0] if len(parts) > 0 else None + baudrate_str = parts[1] if len(parts) > 1 else None + + # Handle third part which might be "8-N-1" format or just databits + if len(parts) > 2 and parts[2]: + third_part = parts[2] + if '-' in third_part: + # Parse "8-N-1" format + dash_parts = third_part.split('-') + if len(dash_parts) >= 3: + databits_str = dash_parts[0] + parity_str = dash_parts[1] + stopbits_str = dash_parts[2] + else: + databits_str = third_part + parity_str = parts[3] if len(parts) > 3 else None + stopbits_str = parts[4] if len(parts) > 4 else None + + flowcontrol_str = ( + parts[-1] + if len(parts) > 3 and databits_str + else (parts[3] if len(parts) > 3 else None) + ) + + # Parse port (required) + if not port or not port.strip(): + raise ValueError("Port cannot be empty") + port = port.strip() + + # Parse baudrate with fallback + try: + if baudrate_str: + baudrate = int(baudrate_str) + if baudrate not in cls.VALID_BAUDRATES: + # Find closest valid baudrate + baudrate = min(cls.VALID_BAUDRATES, key=lambda x: abs(x - baudrate)) + else: + baudrate = defaults['baudrate'] + except (ValueError, TypeError): + baudrate = defaults['baudrate'] + + # Parse databits with validation + try: + if databits_str: + databits = int(databits_str) + if databits not in range(5, 9): # 5-8 are valid + databits = defaults['databits'] + else: + databits = defaults['databits'] + except (ValueError, TypeError): + databits = defaults['databits'] + + # Parse parity with fallback + try: + if parity_str: + parity = Parity(parity_str.upper()) + else: + parity = defaults['parity'] + except (ValueError, AttributeError): + parity = defaults['parity'] + + # Parse stopbits with validation + try: + if stopbits_str: + if stopbits_str == "1.5": + stopbits = 1.5 + else: + stopbits = int(stopbits_str) + if stopbits not in [1, 2]: + stopbits = defaults['stopbits'] + else: + stopbits = defaults['stopbits'] + except (ValueError, TypeError): + stopbits = defaults['stopbits'] + + # Parse flow control with fallback and flexible matching + try: + if flowcontrol_str: + fc_upper = flowcontrol_str.upper().strip() + flowcontrol = None + + # Direct match first + for fc in FlowControl: + if fc.value.upper() == fc_upper: + flowcontrol = fc + break + + # Partial match for common abbreviations + if not flowcontrol: + if fc_upper in ['RTS/CTS', 'RTSCTS', 'RTS', 'CTS', 'HARDWARE']: + flowcontrol = FlowControl.RTS_CTS + elif fc_upper in ['XON/XOFF', 'XONXOFF', 'XON', 'XOFF', 'SOFTWARE']: + flowcontrol = FlowControl.XON_XOFF + elif fc_upper in ['DSR/DTR', 'DSRDTR', 'DSR', 'DTR']: + flowcontrol = FlowControl.DSR_DTR + elif fc_upper in ['NONE', 'N', 'NO', 'OFF']: + flowcontrol = FlowControl.NONE + else: + flowcontrol = defaults['flowcontrol'] + else: + flowcontrol = defaults['flowcontrol'] + except (ValueError, AttributeError): + flowcontrol = defaults['flowcontrol'] + + return UARTSettings( + port=port, + baudrate=baudrate, + databits=databits, + parity=parity, + stopbits=stopbits, + flowcontrol=flowcontrol, + ) + + @classmethod + def validate_port_name(cls, port: str) -> bool: + """Validate if port name follows common patterns""" + # Windows COM ports + if re.match(r'^COM\d+$', port, re.IGNORECASE): + return True + # Linux/Unix serial ports + if re.match(r'^/dev/tty(USB|ACM|S)\d+$', port): + return True + # Generic validation - allow any non-empty string + return bool(port.strip()) + + +def main(): + """Example usage and testing""" + + # Set UTF-8 encoding for stdout/stderr + os.environ['PYTHONIOENCODING'] = 'utf-8' + + # For Python 3.7+, you can also reconfigure stdout/stderr + if hasattr(sys.stdout, 'reconfigure'): + sys.stdout.reconfigure(encoding='utf-8') + sys.stderr.reconfigure(encoding='utf-8') + + test_strings = [ + "COM3", + "COM3:115200", + "COM3:115200,8-N-1,None", + "/dev/ttyUSB0", + "/dev/ttyUSB0:9600", + "/dev/ttyUSB0:9600,8-E-1,RTS/CTS", + "COM1:57600,7-O-2,XON/XOFF", + "/dev/ttyACM0,38400,8-N-1.5,None", + "COM10:921600,8-N-1,DSR/DTR", + ] + + parser = UARTParser() + + print("UART Configuration Parser: (Enter to quit)") + print("=" * 50) + + for uart_str in test_strings: + try: + settings = parser.parse(uart_str) + print(f"✓ Input: {uart_str}") + print(f" Output: {settings}") + print(f" Valid: {parser.validate_port_name(settings.port)}") + print() + except ValueError as e: + print(f"✗ Input: {uart_str}") + print(f" Error: {e}") + print() + + # Interactive mode + print("\nInteractive Mode (press Enter with empty input to exit):") + while True: + try: + user_input = input("Enter UART string: ").strip() + if not user_input: + break + + settings = parser.parse(user_input) + print(f"Parsed: {settings}") + + except ValueError as e: + print(f"Error: {e}") + except KeyboardInterrupt: + break + + print("Goodbye!") + + +if __name__ == "__main__": + main()