Working DT ADC sample WITHOUT resistor divider

This commit is contained in:
Eduard Iten 2025-07-07 12:32:53 +02:00
parent 2c21f1f9cb
commit dcb73c0a25
10 changed files with 264 additions and 88 deletions

View File

@ -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)

View File

@ -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 <adc_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
<samples/drivers/adc/adc_dt/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.

View File

@ -1,6 +1,7 @@
/ { / {
zephyr,user { zephyr,user {
io-channels = <&adc1 1>, <&adc1 12>; io-channels = <&adc1 1>;
io-channel-names = "multi_sense";
}; };
}; };
@ -16,19 +17,12 @@
#address-cells = <1>; #address-cells = <1>;
#size-cells = <0>; #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>; reg = <1>;
zephyr,gain = "ADC_GAIN_1"; zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL"; zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>; zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>; zephyr,resolution = <12>;
}; };
channel@c {
reg = <0xc>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
}; };

View File

@ -0,0 +1,2 @@
CONFIG_ADC=y
CONFIG_LOG=y

View File

@ -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+"

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* 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 = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* 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 = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* 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 = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Wolter HV <wolterhv@gmx.de>
*
* 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 = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};

View File

@ -1,101 +1,58 @@
/* #include <zephyr/kernel.h>
* Copyright (c) 2020 Libre Solar Technologies GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/device.h> #include <zephyr/device.h>
#include <zephyr/devicetree.h> #include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h> #include <zephyr/drivers/adc.h>
#include <zephyr/kernel.h> #include <zephyr/logging/log.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \ LOG_MODULE_REGISTER(adc_dt_example, LOG_LEVEL_DBG);
!DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels)
#error "No suitable devicetree overlay specified"
#endif
#define DT_SPEC_AND_COMMA(node_id, prop, idx) \ /* Get the ADC channel specification from the devicetree */
ADC_DT_SPEC_GET_BY_IDX(node_id, idx), #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);
/* 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)
};
int main(void) int main(void)
{ {
int err; int err;
uint32_t count = 0;
uint16_t buf; uint16_t buf;
struct adc_sequence sequence = { struct adc_sequence sequence = {
.buffer = &buf, .buffer = &buf,
/* buffer size in bytes, not number of samples */
.buffer_size = sizeof(buf), .buffer_size = sizeof(buf),
}; };
/* Configure channels individually prior to sampling. */ if (!device_is_ready(sense_channel.dev)) {
for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) { LOG_ERR("ADC controller device not ready");
if (!adc_is_ready_dt(&adc_channels[i])) {
printk("ADC controller device %s not ready\n", adc_channels[i].dev->name);
return 0; return 0;
} }
err = adc_channel_setup_dt(&adc_channels[i]); err = adc_channel_setup_dt(&sense_channel);
if (err < 0) { if (err < 0) {
printk("Could not setup channel #%d (%d)\n", i, err); LOG_ERR("Could not setup channel #%u (%d)", sense_channel.channel_id, err);
return 0; return 0;
} }
}
#ifndef CONFIG_COVERAGE LOG_INF("ADC channel setup successful!");
while (1) { while (1) {
#else // 1. Explicitly initialize the sequence structure from the devicetree spec.
for (int k = 0; k < 10; k++) { // This sets sequence.channels correctly.
#endif (void)adc_sequence_init_dt(&sense_channel, &sequence);
printk("ADC reading[%u]:\n", count++);
for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
int32_t val_mv;
printk("- %s, channel %d: ", // 2. Perform the read on the ADC device with the now-configured sequence.
adc_channels[i].dev->name, err = adc_read(sense_channel.dev, &sequence);
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) { if (err < 0) {
printk("Could not read (%d)\n", err); LOG_ERR("Could not read (%d)", err);
k_sleep(K_MSEC(1000));
continue; continue;
} }
/* int32_t val_mv = buf;
* If using differential mode, the 16 bit value err = adc_raw_to_millivolts_dt(&sense_channel, &val_mv);
* 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) { if (err < 0) {
printk(" (value in mV not available)\n"); LOG_WRN("Could not convert to millivolts (%d)", err);
} else {
printk(" = %"PRId32" mV\n", val_mv);
}
} }
LOG_INF("ADC reading raw: %d -> %d mV", buf, val_mv);
k_sleep(K_MSEC(1000)); k_sleep(K_MSEC(1000));
} }
return 0; return 0;