diff --git a/software/apps/adc_dt/CMakeLists.txt b/software/apps/adc_dt/CMakeLists.txt new file mode 100644 index 0000000..044d736 --- /dev/null +++ b/software/apps/adc_dt/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/software/apps/adc_dt/README.rst b/software/apps/adc_dt/README.rst new file mode 100644 index 0000000..c66eef6 --- /dev/null +++ b/software/apps/adc_dt/README.rst @@ -0,0 +1,62 @@ +.. zephyr:code-sample:: adc_dt + :name: Analog-to-Digital Converter (ADC) with devicetree + :relevant-api: adc_interface + + Read analog inputs from ADC channels. + +Overview +******** + +This sample demonstrates how to use the :ref:`ADC driver API `. + +Depending on the target board, it reads ADC samples from one or more channels +and prints the readings on the console. If voltage of the used reference can +be obtained, the raw readings are converted to millivolts. + +The pins of the ADC channels are board-specific. Please refer to the board +or MCU datasheet for further details. + +Building and Running +******************** + +The ADC peripheral and pinmux is configured in the board's ``.dts`` file. Make +sure that the ADC is enabled (``status = "okay";``). + +In addition to that, this sample requires an ADC channel specified in the +``io-channels`` property of the ``zephyr,user`` node. This is usually done with +a devicetree overlay. The example overlay in the ``boards`` subdirectory for +the ``nucleo_l073rz`` board can be easily adjusted for other boards. + +Configuration of channels (settings like gain, reference, or acquisition time) +also needs to be specified in devicetree, in ADC controller child nodes. Also +the ADC resolution and oversampling setting (if used) need to be specified +there. See :zephyr_file:`boards/nrf52840dk_nrf52840.overlay +` for an example of +such setup. + +Building and Running for ST Nucleo L073RZ +========================================= + +The sample can be built and executed for the +:zephyr:board:`nucleo_l073rz` as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/adc/adc_dt + :board: nucleo_l073rz + :goals: build flash + :compact: + +To build for another board, change "nucleo_l073rz" above to that board's name +and provide a corresponding devicetree overlay. + +Sample output +============= + +You should get a similar output as below, repeated every second: + +.. code-block:: console + + ADC reading: + - ADC_0, channel 7: 36 = 65mV + +.. note:: If the ADC is not supported, the output will be an error message. diff --git a/software/apps/adc_dt/boards/weact_stm32g431_core.overlay b/software/apps/adc_dt/boards/weact_stm32g431_core.overlay index 2ce8164..e1f4dfa 100644 --- a/software/apps/adc_dt/boards/weact_stm32g431_core.overlay +++ b/software/apps/adc_dt/boards/weact_stm32g431_core.overlay @@ -1,6 +1,7 @@ -/{ +/ { zephyr,user { - io-channels = <&adc1 1>, <&adc1 12>; + io-channels = <&adc1 1>; + io-channel-names = "multi_sense"; }; }; @@ -16,19 +17,12 @@ #address-cells = <1>; #size-cells = <0>; - channel@1 { + /* This defines channel 1 on adc1. The "1" in io-channels refers to this reg value. */ + adc_channel_1: channel@1 { reg = <1>; zephyr,gain = "ADC_GAIN_1"; zephyr,reference = "ADC_REF_INTERNAL"; zephyr,acquisition-time = ; zephyr,resolution = <12>; }; - - channel@c { - reg = <0xc>; - zephyr,gain = "ADC_GAIN_1"; - zephyr,reference = "ADC_REF_INTERNAL"; - zephyr,acquisition-time = ; - zephyr,resolution = <12>; - }; }; \ No newline at end of file diff --git a/software/apps/adc_dt/prj.conf b/software/apps/adc_dt/prj.conf new file mode 100644 index 0000000..8356908 --- /dev/null +++ b/software/apps/adc_dt/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ADC=y +CONFIG_LOG=y \ No newline at end of file diff --git a/software/apps/adc_dt/sample.yaml b/software/apps/adc_dt/sample.yaml new file mode 100644 index 0000000..1592685 --- /dev/null +++ b/software/apps/adc_dt/sample.yaml @@ -0,0 +1,53 @@ +sample: + name: ADC devicetree driver sample +tests: + sample.drivers.adc.adc_dt: + tags: + - adc + depends_on: adc + platform_allow: + - nucleo_l073rz + - disco_l475_iot1 + - cc3220sf_launchxl + - cc3235sf_launchxl + - cy8cproto_063_ble + - stm32l496g_disco + - stm32h735g_disco + - nrf51dk/nrf51822 + - nrf52840dk/nrf52840 + - nrf54l15dk/nrf54l15/cpuapp + - nrf54h20dk/nrf54h20/cpuapp + - ophelia4ev/nrf54l15/cpuapp + - mec172xevb_assy6906 + - gd32f350r_eval + - gd32f450i_eval + - gd32vf103v_eval + - gd32f403z_eval + - esp32_devkitc/esp32/procpu + - esp32s2_saola + - esp32c3_devkitm + - gd32l233r_eval + - lpcxpresso55s36 + - mr_canhubk3 + - longan_nano + - longan_nano/gd32vf103/lite + - rd_rw612_bga + - frdm_mcxn947/mcxn947/cpu0 + - mcx_n9xx_evk/mcxn947/cpu0 + - frdm_mcxc242 + - ucans32k1sic + - xg24_rb4187c + - xg29_rb4412a + - raytac_an54l15q_db/nrf54l15/cpuapp + - frdm_mcxa166 + - frdm_mcxa276 + integration_platforms: + - nucleo_l073rz + - nrf52840dk/nrf52840 + harness: console + timeout: 10 + harness_config: + type: multi_line + regex: + - "ADC reading\\[\\d+\\]:" + - "- .+, channel \\d+: -?\\d+" diff --git a/software/apps/adc_dt/socs/esp32_procpu.overlay b/software/apps/adc_dt/socs/esp32_procpu.overlay new file mode 100644 index 0000000..272d0d7 --- /dev/null +++ b/software/apps/adc_dt/socs/esp32_procpu.overlay @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Wolter HV + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr,user { + io-channels = <&adc0 0>; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_4"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/software/apps/adc_dt/socs/esp32c3.overlay b/software/apps/adc_dt/socs/esp32c3.overlay new file mode 100644 index 0000000..272d0d7 --- /dev/null +++ b/software/apps/adc_dt/socs/esp32c3.overlay @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Wolter HV + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr,user { + io-channels = <&adc0 0>; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_4"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/software/apps/adc_dt/socs/esp32s2.overlay b/software/apps/adc_dt/socs/esp32s2.overlay new file mode 100644 index 0000000..272d0d7 --- /dev/null +++ b/software/apps/adc_dt/socs/esp32s2.overlay @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Wolter HV + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr,user { + io-channels = <&adc0 0>; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_4"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/software/apps/adc_dt/socs/esp32s3_procpu.overlay b/software/apps/adc_dt/socs/esp32s3_procpu.overlay new file mode 100644 index 0000000..272d0d7 --- /dev/null +++ b/software/apps/adc_dt/socs/esp32s3_procpu.overlay @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Wolter HV + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr,user { + io-channels = <&adc0 0>; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1_4"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; diff --git a/software/apps/adc_dt/src/main.c b/software/apps/adc_dt/src/main.c index ad8a446..dc2604c 100644 --- a/software/apps/adc_dt/src/main.c +++ b/software/apps/adc_dt/src/main.c @@ -1,101 +1,58 @@ -/* - * Copyright (c) 2020 Libre Solar Technologies GmbH - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include - +#include #include #include #include -#include -#include -#include +#include -#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ - !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels) -#error "No suitable devicetree overlay specified" -#endif +LOG_MODULE_REGISTER(adc_dt_example, LOG_LEVEL_DBG); -#define DT_SPEC_AND_COMMA(node_id, prop, idx) \ - ADC_DT_SPEC_GET_BY_IDX(node_id, idx), - -/* Data of ADC io-channels specified in devicetree. */ -static const struct adc_dt_spec adc_channels[] = { - DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, - DT_SPEC_AND_COMMA) -}; +/* Get the ADC channel specification from the devicetree */ +#define SENSE_NODE DT_PATH(zephyr_user) +static const struct adc_dt_spec sense_channel = ADC_DT_SPEC_GET_BY_NAME(SENSE_NODE, multi_sense); int main(void) { int err; - uint32_t count = 0; uint16_t buf; struct adc_sequence sequence = { .buffer = &buf, - /* buffer size in bytes, not number of samples */ .buffer_size = sizeof(buf), }; - /* Configure channels individually prior to sampling. */ - for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) { - if (!adc_is_ready_dt(&adc_channels[i])) { - printk("ADC controller device %s not ready\n", adc_channels[i].dev->name); - return 0; - } - - err = adc_channel_setup_dt(&adc_channels[i]); - if (err < 0) { - printk("Could not setup channel #%d (%d)\n", i, err); - return 0; - } + if (!device_is_ready(sense_channel.dev)) { + LOG_ERR("ADC controller device not ready"); + return 0; } -#ifndef CONFIG_COVERAGE + err = adc_channel_setup_dt(&sense_channel); + if (err < 0) { + LOG_ERR("Could not setup channel #%u (%d)", sense_channel.channel_id, err); + return 0; + } + + LOG_INF("ADC channel setup successful!"); + while (1) { -#else - for (int k = 0; k < 10; k++) { -#endif - printk("ADC reading[%u]:\n", count++); - for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) { - int32_t val_mv; + // 1. Explicitly initialize the sequence structure from the devicetree spec. + // This sets sequence.channels correctly. + (void)adc_sequence_init_dt(&sense_channel, &sequence); - printk("- %s, channel %d: ", - adc_channels[i].dev->name, - adc_channels[i].channel_id); - - (void)adc_sequence_init_dt(&adc_channels[i], &sequence); - - err = adc_read_dt(&adc_channels[i], &sequence); - if (err < 0) { - printk("Could not read (%d)\n", err); - continue; - } - - /* - * If using differential mode, the 16 bit value - * in the ADC sample buffer should be a signed 2's - * complement value. - */ - if (adc_channels[i].channel_cfg.differential) { - val_mv = (int32_t)((int16_t)buf); - } else { - val_mv = (int32_t)buf; - } - printk("%"PRId32, val_mv); - err = adc_raw_to_millivolts_dt(&adc_channels[i], - &val_mv); - /* conversion to mV may not be supported, skip if not */ - if (err < 0) { - printk(" (value in mV not available)\n"); - } else { - printk(" = %"PRId32" mV\n", val_mv); - } + // 2. Perform the read on the ADC device with the now-configured sequence. + err = adc_read(sense_channel.dev, &sequence); + if (err < 0) { + LOG_ERR("Could not read (%d)", err); + k_sleep(K_MSEC(1000)); + continue; } + int32_t val_mv = buf; + err = adc_raw_to_millivolts_dt(&sense_channel, &val_mv); + if (err < 0) { + LOG_WRN("Could not convert to millivolts (%d)", err); + } + + LOG_INF("ADC reading raw: %d -> %d mV", buf, val_mv); + k_sleep(K_MSEC(1000)); } return 0;