ADC

The phyCORE-RT1170 features two 12-bit LPADC (Low-Power Analog-to-Digital Converter) instances, LPADC1 and LPADC2. Each ADC capable pin can be muxed to one or more LPADC channels via the SoC’s IOMUX.

The phyBOARD-Atlas RT1170 carrier board routes the LPADC interface out at pin GPIO_AD_12, which is connected to the analog output of U12 (MAX4372FEUK+T), a current-sense amplifier.

For detailed hardware-level information, refer to the 10.2 ADC chapter in the Hardware Manual.

Reading a Channel via Shell

The example configures LPADC1 channel 3A on pin GPIO_AD_12, entirely from devicetree, and exposes it through a single shell command. On the phyBOARD-Atlas RT1170 carrier board, this reads the sensed-current output of U12:

uart:~$ adc_read
ADC raw value: 2048 = 900 mV

Each invocation performs one conversion and prints both the raw sample and the converted millivolt value. The channel is configured on first use, so the very first call takes care of adc_channel_setup_dt() before sampling.

ADC Channel Configuration

The channel is defined once, at build time, using the zephyr,user devicetree node’s io-channels property:

/ {
        zephyr,user {
                io-channels = <&lpadc1 0>;
        };
};

&lpadc1 {
        status = "okay";
        #address-cells = <1>;
        #size-cells = <0>;

        channel@0 {
                reg = <0>;
                zephyr,gain = "ADC_GAIN_1";
                zephyr,reference = "ADC_REF_EXTERNAL0";
                zephyr,vref-mv = <1800>;
                zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_TICKS, 7)>;
                zephyr,resolution = <12>;
                zephyr,input-positive = <MCUX_LPADC_CH3A>;
        };
};

The struct adc_dt_spec for this channel is retrieved with ADC_DT_SPEC_GET_BY_IDX(), which pulls the device, channel ID, gain, reference, resolution and acquisition time straight out of the devicetree node above, so no manual configuration is required in the source code.

Note

The mcux-lpadc driver only accepts ADC_REF_EXTERNAL0 or ADC_REF_EXTERNAL1 as zephyr,reference; ADC_REF_INTERNAL is rejected outright.

Channel Setup on First Use

The channel is configured with adc_channel_setup_dt() the first time the command runs, and the result is cached in a static flag so that subsequent invocations skip straight to sampling.

Reading the Channel

Each call to adc_read performs one conversion:

  • adc_sequence_init_dt() prepares an adc_sequence from the devicetree-defined resolution.

  • adc_read_dt() triggers the conversion and stores the raw result.

  • adc_raw_to_millivolts_dt() converts the raw value to millivolts using the channel’s configured gain and reference voltage.

The raw value, and the converted millivolt reading when available, are printed to the shell.

The mcux-lpadc driver writes exactly one uint16_t per sample into the sequence buffer, regardless of the configured resolution. Declaring the sample buffer as a wider type, such as uint32_t, leaves the upper bytes as uninitialized stack contents and produces nonsensical raw and millivolt readings.

Source Code

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/shell/shell.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(adc_read, CONFIG_APP_LOG_LEVEL);

/*
 * Get ADC channel configuration from the devicetree zephyr,user node's
 * io-channels property. This is mandatory.
 */
#if !DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels)
#error "Unsupported board: io-channels not defined for the zephyr,user node"
#endif

static const struct adc_dt_spec adc_chan0 = ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), 0);
static bool adc_chan0_configured;

static int cmd_adc_read(const struct shell *sh, size_t argc, char **argv)
{
        int ret;
        uint16_t buf;
        int32_t val_mv;
        struct adc_sequence sequence = {
                .buffer = &buf,
                /* buffer size in bytes, not number of samples */
                .buffer_size = sizeof(buf),
        };

        ARG_UNUSED(argc);
        ARG_UNUSED(argv);

        if (!adc_is_ready_dt(&adc_chan0)) {
                shell_error(sh, "%s is not ready", adc_chan0.dev->name);
                return -ENODEV;
        }

        if (!adc_chan0_configured) {
                ret = adc_channel_setup_dt(&adc_chan0);
                if (ret < 0) {
                        shell_error(sh, "Unable to configure %s channel %d",
                                    adc_chan0.dev->name, adc_chan0.channel_id);
                        return ret;
                }
                adc_chan0_configured = true;
        }

        ret = adc_sequence_init_dt(&adc_chan0, &sequence);
        if (ret < 0) {
                shell_error(sh, "Unable to initialize ADC sequence: %d", ret);
                return ret;
        }

        ret = adc_read_dt(&adc_chan0, &sequence);
        if (ret < 0) {
                shell_error(sh, "Failed to read %s: %d", adc_chan0.dev->name, ret);
                return ret;
        }

        val_mv = (int32_t)buf;
        ret = adc_raw_to_millivolts_dt(&adc_chan0, &val_mv);
        if (ret < 0) {
                shell_print(sh, "ADC raw value: %u (mV conversion not available)", buf);
        } else {
                shell_print(sh, "ADC raw value: %u = %d mV", buf, val_mv);
        }

        return 0;
}

SHELL_CMD_ARG_REGISTER(adc_read, NULL,
                       "Read the example ADC channel (GPIO_AD_12, LPADC1 CH3A)",
                       cmd_adc_read, 1, 0);