Setting up multiple ADC channels in the device tree

Hi,

I am working on setting up multiple ADC channels in the device tree. My starting point is here, which seemed to be an unofficial ADC example that many ADC-related posts referred to.

I can make it work for a single-ended mode as well as a differential mode involving two channels in the source code, but it is difficult to find a suitable guide for setting up the ADC channels in the device tree. After reading off many posts, I came up with the following lines of code in the overlay file:

&adc {
    // compatible = "nordic_nrf_saadc";
    status = "okay";
    io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 3>, <&adc 4>, <&adc 5>, <&adc 6>, <&adc 7>;
    io-channel-names = "VCM2", "READ2_MCU", "READ3_MCU", "VCM3", "VCM1", "READ1_MCU", "VCM4", "READ4_MCU";
};
I set these io-channels and they all show up in zephyr.dts but unfortunately I don't know how to utilize these names yet.
 
Note that I commented out the compatible = "nordic_nrf_saadc", since it caused binding failure and system crash. 

Firstly, I wonder if my approach allows to set up all 8 ADC channels and how to configure all of them in the source code and how I can utilize the names I assigned to each ADC channels. My current source code looks like:
#define ADC_DEVICE_NAME "ADC_0"
#define ADC_RESOLUTION 10
#define ADC_GAIN ADC_GAIN_1_4
#define ADC_REFERENCE ADC_REF_VDD_1_4
#define ADC_ACQUISITION_TIME ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)

#define AIN0_VCM_CHANNEL_ID 0
#define AIN0_VCM_CHANNEL_INPUT NRF_SAADC_INPUT_AIN0
#define AIN1_READ2_MCU_CHANNEL_ID 1
#define AIN1_READ2_MCU_CHANNEL_INPUT NRF_SAADC_INPUT_AIN1

static const struct adc_channel_cfg m_read2_channel_cfg = {
    .gain = ADC_GAIN,
    .reference = ADC_REFERENCE,
    .acquisition_time = ADC_ACQUISITION_TIME,
    .channel_id = AIN1_READ2_MCU_CHANNEL_ID,
    .differential = 1,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
    .input_positive = AIN1_READ2_MCU_CHANNEL_INPUT,
    .input_negative = AIN0_VCM_CHANNEL_INPUT,
#endif
};

#define BUFFER_SIZE 8
static int16_t m_sample_buffer[BUFFER_SIZE];

const struct adc_sequence_options sequence_opts = {
    .interval_us = 0,
    .callback = NULL,
    .user_data = NULL,
    .extra_samplings = 7,
};

static int adc_sample(void)
{
    int ret;

    const struct adc_sequence sequence = {
        .options = &sequence_opts,
        .channels = BIT(AIN1_READ2_MCU_CHANNEL_ID),
        .buffer = m_sample_buffer,
        .buffer_size = sizeof(m_sample_buffer),
        .resolution = ADC_RESOLUTION,
    };

    if (!adc_dev) {
        return -1;
    }

    ret = adc_read(adc_dev, &sequence);
    printk("\nADC read err: %d\n", ret);

    /* Print the AIN0 values */
    printk("ADC raw value: ");
    for (int i = 0; i < BUFFER_SIZE; i++) {
        printk("%d ", m_sample_buffer[i]);
    }
   
    printf("\n Measured voltage: ");
    for (int i = 0; i < BUFFER_SIZE; i++) {
        float adc_voltage = 0;
        adc_voltage = (float)(((float)m_sample_buffer[i] / 1023.0f) *
                      3600.0f);
        printk("%f ",adc_voltage);
    }
    printk("\n");

    return ret;
}

int read_adc(void)
{
    int err;

    printk("nRF53 SAADC sampling AIN1 (READ2_MCU)\n");

    adc_dev = device_get_binding(ADC_DEVICE_NAME);
    if (!adc_dev) {
        printk("device_get_binding ADC_0 failed\n");
    }
    err = adc_channel_setup(adc_dev, &m_read2_channel_cfg);
    if (err) {
        printk("Error in adc setup: %d\n", err);
    }

    /* Trigger offset calibration
     * As this generates a _DONE and _RESULT event
     * the first result will be incorrect.
     */
    NRF_SAADC_S->TASKS_CALIBRATEOFFSET = 1;
    while (1) {
        err = adc_sample();
        if (err) {
            printk("Error in adc sampling: %d\n", err);
        }
        k_sleep(K_MSEC(500));
    }
}
Lastly, is it better to instantiate each channel into an individual node like the following? If so, can I use the node name freely instead of n: node? 
/ {
    n: node {
        compatible = " ";
        io-channels = <&adc 4>;
        label = "AIN_0";
    };
};
Thank you in advance!
Parents
  • Hello, Ted!

    Have you had a look at the Device Tree API documentation for IO Channels? Specifically the DT_IO_CHANNELS_INPUT_BY_NAME macro. I haven't tried these myself, but from what I can see they fit your requirements.

    Regarding your second question I believe you can instantiate all the channels in a single node like they have done in the example code in the documentation.

    Best regards,
    Carl Richard

  • Hi Carl,

    Thank you for the reply. In fact, I used the DT macro to enable the GPIO pins but was unsuccessful with the ADC stuff. The only relevant part was:

    &adc {
        // compatible = "nordic_nrf_saadc"; // only used to instantiate the NRF_SAADC-peripheral on the chip
        status = "okay";
        // io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 3>, <&adc 4>, <&adc 5>, <&adc 6>, <&adc 7>;
        // io-channel-names = "VCM2", "READ2_MCU", "READ3_MCU", "VCM3", "VCM1", "READ1_MCU", "VCM4", "READ4_MCU";
    };
    Please note that it didn't need the io-channels to be specified in the overlay file, but those were configured in the source code side instead and it worked fine:
    #define ADC_DEVICE_NAME "ADC_0"
    #define ADC_RESOLUTION 10
    #define ADC_GAIN ADC_GAIN_1_4
    #define ADC_REFERENCE ADC_REF_VDD_1_4
    #define ADC_ACQUISITION_TIME ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)
    
    // READ4 Channel
    #define AIN6_VCM_CHANNEL_ID 6
    #define AIN6_VCM_CHANNEL_INPUT NRF_SAADC_INPUT_AIN6
    #define AIN7_READ4_MCU_CHANNEL_ID 7
    #define AIN7_READ4_MCU_CHANNEL_INPUT NRF_SAADC_INPUT_AIN7
    
    static const struct adc_channel_cfg m_read4_channel_cfg = {
        .gain = ADC_GAIN,
        .reference = ADC_REFERENCE,
        .acquisition_time = ADC_ACQUISITION_TIME,
        .channel_id = AIN7_READ4_MCU_CHANNEL_ID,
        .differential = 1,
    #if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
        .input_positive = AIN7_READ4_MCU_CHANNEL_INPUT,
        .input_negative = AIN6_VCM_CHANNEL_INPUT,
    #endif
    };
    
    err = adc_channel_setup(adc_dev, &m_read4_channel_cfg);
    if (err) {
        printk("Error in setting up the READ4_MCU: %d\n", err);
    }
    So my question is, what are the benefits of instantiating these ADC channels in the device tree rather than doing it in the source code?    
Reply
  • Hi Carl,

    Thank you for the reply. In fact, I used the DT macro to enable the GPIO pins but was unsuccessful with the ADC stuff. The only relevant part was:

    &adc {
        // compatible = "nordic_nrf_saadc"; // only used to instantiate the NRF_SAADC-peripheral on the chip
        status = "okay";
        // io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 3>, <&adc 4>, <&adc 5>, <&adc 6>, <&adc 7>;
        // io-channel-names = "VCM2", "READ2_MCU", "READ3_MCU", "VCM3", "VCM1", "READ1_MCU", "VCM4", "READ4_MCU";
    };
    Please note that it didn't need the io-channels to be specified in the overlay file, but those were configured in the source code side instead and it worked fine:
    #define ADC_DEVICE_NAME "ADC_0"
    #define ADC_RESOLUTION 10
    #define ADC_GAIN ADC_GAIN_1_4
    #define ADC_REFERENCE ADC_REF_VDD_1_4
    #define ADC_ACQUISITION_TIME ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)
    
    // READ4 Channel
    #define AIN6_VCM_CHANNEL_ID 6
    #define AIN6_VCM_CHANNEL_INPUT NRF_SAADC_INPUT_AIN6
    #define AIN7_READ4_MCU_CHANNEL_ID 7
    #define AIN7_READ4_MCU_CHANNEL_INPUT NRF_SAADC_INPUT_AIN7
    
    static const struct adc_channel_cfg m_read4_channel_cfg = {
        .gain = ADC_GAIN,
        .reference = ADC_REFERENCE,
        .acquisition_time = ADC_ACQUISITION_TIME,
        .channel_id = AIN7_READ4_MCU_CHANNEL_ID,
        .differential = 1,
    #if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
        .input_positive = AIN7_READ4_MCU_CHANNEL_INPUT,
        .input_negative = AIN6_VCM_CHANNEL_INPUT,
    #endif
    };
    
    err = adc_channel_setup(adc_dev, &m_read4_channel_cfg);
    if (err) {
        printk("Error in setting up the READ4_MCU: %d\n", err);
    }
    So my question is, what are the benefits of instantiating these ADC channels in the device tree rather than doing it in the source code?    
Children
No Data
Related