Colibri imx6ULL and Iris Carrier - ADC problems

Please we need assitance from Technical Support, urgent!

We have a Colibri module (imx6ULL) in an Iris Carrier Board and we want to test de ADC capabilities in order to make later a custom carrier board. We have been struggling with the ADC and the LibIIO for days now and we can´t seem to make it work. Our goal is to capture samples from the 4 channels of the ADC available from the pins of the Iris (analogIN0, IN1, IN2 and IN3) at a periodic rate (approximately 2000 samples per second), thats all!
We have already read everything in the forum, the manuals, etc and nothing seems to work.
The problem we have is that we are not able to use the “buffer” object from the LibIIO framework, which seems to be the best approach. When we try to create the buffer object, the function returns an errno: 22. We try changing the linux-kernel options enabling all that refers to IIO support, buffer, etc, and nothing. We try changing the options of the device tree using the examples, and nothing. We try enabling the trigger and some patches found in the web, and nothing. We are getting out of options here.
We are using the following:

Carrier: Iris 2.0
LibIIO version: 0.24
MACHINE = “colibri-imx6ull”
DISTRO = “tdx-x11-rt”
DISTRO_VERSION = “5.7.0-devel-20220915124239+build.0”

Device tree (ADC section) in the imx6ull-colibri.dtsi:
&adc1 {
num-channels = <10>;
vref-supply = <&reg_module_3v3_avdd>;
pinctrl-names = “default”;
pinctrl-0 = <&pinctrl_adc1>;
};
pinctrl_adc1: adc1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 0x3000 /* SODIMM 8 /
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0x3000 /
SODIMM 6 /
MX6UL_PAD_GPIO1_IO08__GPIO1_IO08 0x3000 /
SODIMM 4 /
MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x3000 /
SODIMM 2 */
>;
};

So, i want to ask a few questions:

  1. Is it possible to use the buffer object in LibIIO with the imx6ULL module or not? We read something about a limitation in the continous mode of the ADC with the imx6ULL module, but on the other hand, in the reference manual of the SOC there is a register that says the continous mode can be enabled.
  2. If the approach in point 1) is not possible, which approach is the best to acomplish our goal with the hardware we have? We buy this module in order to make a professional product, and send it to production in a near future. We need to make this work by all means.
  3. Is there an image already cooked of Yocto project with this functionality enabled for the Iris Carrier using the imx6ULL module so we can upload it to the module and test it? We need something that works so we can build our application over that.

Hope a soon response.

Best regards,
Martin

1 Like

Hi @martin_mig ,

Thanks for the detailed information!

If you are trying to use the sysfs (echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable), the issue may be related to the lack of IIO trigger support on the kernel, to make it work you need to recompile the kernel adding the flag CONFIG_IIO_SYSFS_TRIGGER. You can find information on how to do it on this article.

After booting the new kernel with the flag enabled, you need to create the trigger using the following commands:

echo 0 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger 
cat /sys/bus/iio/devices/trigger0/name > /sys/bus/iio/devices/iio:device0/trigger/current_trigger

With this, you now can follow the instructions provided here to enable the continuous mode:

echo 1 > /sys/bus/iio/devices/iio\:device0/scan_elements/in_voltage0_en 
echo 100 > /sys/bus/iio/devices/iio\:device0/buffer/length 
echo 1 > /sys/bus/iio/devices/iio\:device0/buffer/enable

In the end, you can read the buffer data using the following command:

hexdump -e '"iio0 :" 8/2 "%04x " "\n"' /dev/iio:device0

Please let me know if it works for you or if you need any help from us.

Best regards,
Daniel Morais

1 Like

Hi Daniel_m,

We tried your proposal and it worked!!!. We managed to enable the CONFIG_IIO_SYSFS_TRIGGER in the kernel and tried your commands and it worked,
but exclusively using the shell commands that you provided us.

What we are trying now is to make it work using the LibIIO functions described on its website in our code.
So far, we are still getting the same error when we try to create the buffer object using “iio_device_create_buffer” → errno: 22 is the output we got.
We tried various combinations of shell commands and our code, but we are getting the same result.
Finally, we tried translating the shell commands that worked into LibIIO functions in our code, but still nothing.

Here I have attached the output of iio_info and my code.
We honestly don´t know where is the problem neither how to solve it.

iio_info output:

root@colibri-imx6ull-06487386:~# iio_info
Library version: 0.24 (git tag: c4498c2)
Compiled with backends: local xml ip usb
IIO context created with local backend.
Backend version: 0.24 (git tag: c4498c2)
Backend description string: Linux colibri-imx6ull-06487386 5.4.193-rt74-5.7.0-devel+git.5a24da287b86 #1 SMP PREEMPT_RT Wed Jun 29 16:15:02 UTC 2022 armv7l
IIO context has 2 attributes:
local,kernel: 5.4.193-rt74-5.7.0-devel+git.5a24da287b86
uri: local:
IIO context has 2 devices:
iio:device0: 2198000.adc (buffer capable)
11 channels found:
voltage0: (input, index: 0, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 74
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage1: (input, index: 1, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 41
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage2: (input, index: 2, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 0
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage3: (input, index: 3, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 1024
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage4: (input, index: 4, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 4095
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage5: (input, index: 5, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 4095
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage6: (input, index: 6, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 4071
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage7: (input, index: 7, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 1277
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage8: (input, index: 8, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 53
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
voltage9: (input, index: 9, format: le:u12/16>>0)
3 channel-specific attributes found:
attr 0: raw value: 3
attr 1: sampling_frequency value: 58098
attr 2: scale value: 0.805664062
conversion: (input, WARN:iio_channel_get_type()=UNKNOWN)
1 channel-specific attributes found:
attr 0: mode value: low-power
3 device-specific attributes found:
attr 0: consumers ERROR: Input/output error (5)
attr 1: sampling_frequency_available value: 206250 58098 29676 15000 7541
attr 2: suppliers ERROR: Input/output error (5)
1 buffer-specific attributes found:
attr 0: data_available value: 0
1 debug attributes found:
debug attr 0: direct_reg_access value: 0x89
ERROR: checking for trigger : Input/output error (5)
iio_sysfs_trigger:
0 channels found:
4 device-specific attributes found:
attr 0: add_trigger ERROR: Permission denied (13)
attr 1: consumers ERROR: Input/output error (5)
attr 2: remove_trigger ERROR: Permission denied (13)
attr 3: suppliers ERROR: Input/output error (5)
No trigger on this device


Code:(for simplicity we have omitted the verification of the returned value of all the functions, but we are checking it).
int main()
{
struct iio_context* ctx;
struct iio_device* dev;
struct iio_buffer* iio_buf;
struct iio_channel *chn0;
struct iio_device *iio_sysfs_trigger;
struct iio_device *trigger0;

ctx = iio_create_local_context();		//this one works fine
dev = iio_context_get_device(ctx, 0);	//this one works fine
chn0 = iio_device_get_channel(dev, 0);	//this one works fine

//echo 0 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
//The functions below tries to make the same effect as the shell command above
iio_sysfs_trigger = iio_context_find_device(ctx, "iio_sysfs_trigger");	//this one works fine
iio_device_attr_write(iio_sysfs_trigger, "add_trigger", "0");			//this one works fine

iio_channel_enable(chn0);	//this one works fine

//cat /sys/bus/iio/devices/trigger0/name > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
//The functions below tries to make the same effect as the shell command above
trigger0 = iio_context_find_device(ctx, "trigger0");	//this one works fine
iio_device_set_trigger(dev, trigger0);					//this one works fine

iio_buf = iio_device_create_buffer(dev, 1024, false);	//Errno: 22

...(continued with some code using the buffer that fails because the above function)

}

Please suggest me the solution as early as possible.
Waiting for your reply soon

Thanks and regards,
Martin

Throw a iio_channel_is_scan_element(chn0) test before you do your iio_channel_enable(chn0). I think only scan enabled channels can do that buffer streaming stuff?

– Dave

Dear DaveM and daniel_m.tx

Thank you both for your replies. We managed to get it working just by changing the DISTRO of the SO, we were using the tdx-x11-rt (Real Time) and we rolled back to the tdx-x11 with the CONFIG_IIO_SYSFS_TRIGGER set in “yes” and it worked!

Here is our code:
void ADC_init(void){
// habilitar los canales necesarios y deshabilitar los no usados.

ctx = iio_create_local_context();
dev = iio_context_get_device(ctx, DEVICE_LOCAL);

// inicializamos los punteros a los canales
chn0 = iio_device_get_channel(dev, ANALOG_IN0);
chn1 = iio_device_get_channel(dev, ANALOG_IN1);
chn2 = iio_device_get_channel(dev, ANALOG_IN2);
chn3 = iio_device_get_channel(dev, ANALOG_IN3);
chn_NOTUSED1 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED1);
chn_NOTUSED2 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED2);
chn_NOTUSED3 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED3);
chn_NOTUSED4 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED4);
chn_NOTUSED5 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED5);
chn_NOTUSED6 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED6);
chn_NOTUSED7 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED7);

iio_channel_enable(chn0);
iio_channel_disable(chn1);
iio_channel_disable(chn2);
iio_channel_disable(chn3);
iio_channel_disable(chn_NOTUSED1);
iio_channel_disable(chn_NOTUSED2);
iio_channel_disable(chn_NOTUSED3);
iio_channel_disable(chn_NOTUSED4);
iio_channel_disable(chn_NOTUSED5);
iio_channel_disable(chn_NOTUSED6);
iio_channel_disable(chn_NOTUSED7);

ssize_t ret;
struct iio_device *iio_sysfs_trigger;
struct iio_device *trigger0;
const struct iio_device *current_trigger;

//pregunto si ya esta seteado el current_trigger en el device “dev”
ret = iio_device_get_trigger(dev, &current_trigger);
if (ret < 0) {
printf(“Error al obtener el current trigger\n”);
return;
}

//si no tiene seteado ningun trigger
if (!current_trigger)
{
// obtengo puntero al sysfs_trigger
iio_sysfs_trigger = iio_context_find_device(ctx, “iio_sysfs_trigger”);
if (!iio_sysfs_trigger) {
printf(“Trigger %s not found\n”, “iio_sysfs_trigger”);
iio_context_destroy(ctx);
return;
}
//echo 0 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
// averiguo si ya existe el trigger0
trigger0 = iio_context_find_device(ctx, “trigger0”);
if (!trigger0) {
printf(“El trigger0 no existía.\n”);
// intento crearlo
ret = iio_device_attr_write_bool(iio_sysfs_trigger, “add_trigger”, false);
if (ret < 0) {
printf(“No se pudo escribir el atributo add_trigger en iio_sysfs_trigger\n”);
return;
}
}

   //cat /sys/bus/iio/devices/trigger0/name > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
    ret = iio_device_set_trigger(dev, trigger0);
    if (ret < 0) {
        printf("No se pudo setear el trigger\n");
        return;
    }
}

// buffers
ret = iio_device_set_kernel_buffers_count(dev, 1);
if (ret < 0) {
char buf_test[256];
iio_strerror(-(int)ret, buf_test, sizeof(buf_test));
printf(“set_kernel_buffers failed : %s\n”, buf_test);
return;
}
printf(“* Creating non-cyclic IIO buffers with 1 MiS\n”);

// debemos crear un buffer que nos permita almacenar una determinada
// cantidad de muestras de los 4 canales activos, 2 bytes por cada canal.
iio_buf = iio_device_create_buffer(dev, 1024, false);
if (!iio_buf) {
char buf_test[256];
iio_strerror(-(int)errno, buf_test, sizeof(buf_test));
printf(“Create buffer failed : %s\n”, buf_test);
}
}

Now we are configuring only one channel of the ADC. We tried to enable all four of the available channels and we got another error. We tried to enable two channels at a time and the same error.

In the ADC Linux post in toradex i found this:

And in that pdf i found this:

I want to know the following because we are a bit lost:

  1. With this hardware (imx6ULL) is it possible to enable the 4 channels of the ADC at once and then use the same buffer attached to the device to read the samples of all channels?

  2. In case that it isn’t possible, the procedure would be to enable one channel at a time and then creating a buffer each time we want to read the samples or what the procedure would look like instead?

Thanks in advance.
Hope a soon response.

Martin

Hi @martin_mig ,

Thanks for the detailed information!

About your questions, I’m looking internally into them, and as soon as I have a proper response I came back to you here.

Best regards,
Daniel Morais

Hi @martin_mig ,

Can you please share with us the exactly error when you try to create the buffer with more than one channel?

Also, we found this on the libiio documentation page:

If the Buffer object has been created from a device with input channels, then it must be updated first. This is done with the iio_buffer_refill() function.

Did you guys already try the iio_buffer_refill function?

Best regards,
Daniel Morais

Hi daniel_m

The problem is when we attemped to create the buffer with “iio_device_create_buffer” after we enabled the channels we need (we enabled the ones we need and disable the rest). We got a NULL pointer in iio_buf and errno:22, which means “Invalid argument” (based on what we have found on the internet). The initialization of the ADC (enabling channels, creating the buffer, etc) is done in the ADC_init() function. The “iio_buffer_refill()” function is executed later within a while(true) once the buffer have been created. This code works if we only enabled one channel. But if we enable more than one, it throws that same error.

We also tried to skip the return when the iio_buf is NULL, and try to execute the “iio_buffer_refill()” function anyway with a NULL argument, and it fails.

This code below is the one we got error.

//defines 
#define ANALOG_IN0            0
#define ANALOG_IN1            1
#define ANALOG_IN_NOTUSED1    2
#define ANALOG_IN_NOTUSED2    3
#define ANALOG_IN_NOTUSED3    4
#define ANALOG_IN_NOTUSED4    5
#define ANALOG_IN_NOTUSED5    6
#define ANALOG_IN_NOTUSED6    7
#define ANALOG_IN2            8
#define ANALOG_IN3            9
#define ANALOG_IN_NOTUSED7    10

//global variables
struct iio_context* ctx;
struct iio_device* dev;
struct iio_buffer* iio_buf;
struct iio_channel *chn0;
struct iio_channel *chn1;
struct iio_channel *chn2;
struct iio_channel *chn3;
struct iio_channel *chn_NOTUSED1;
struct iio_channel *chn_NOTUSED2;
struct iio_channel *chn_NOTUSED3;
struct iio_channel *chn_NOTUSED4;
struct iio_channel *chn_NOTUSED5;
struct iio_channel *chn_NOTUSED6;
struct iio_channel *chn_NOTUSED7;

bool ADC_init(void)
{
    ssize_t ret;
    struct iio_device *iio_sysfs_trigger;
    struct iio_device *trigger0;
    const struct iio_device *current_trigger;
    ctx = iio_create_local_context();
    dev = iio_context_get_device(ctx, DEVICE_LOCAL);

   //pregunto si ya esta seteado el current_trigger en el device "dev"
    ret = iio_device_get_trigger(dev, &current_trigger);
    if (ret < 0) {
        char buf_test[256];
        iio_strerror(-(int) ret, buf_test, sizeof(buf_test));
        printf("Error al obtener el current trigger : %s\n", buf_test);
    }

   //si no tiene seteado ningun trigger
    if (!current_trigger)
    {
        // obtengo puntero al sysfs_trigger
        iio_sysfs_trigger = iio_context_find_device(ctx, "iio_sysfs_trigger");
        if (!iio_sysfs_trigger) {
            printf("Trigger %s not found\n", "iio_sysfs_trigger");
            goto free_ctx_dev;
        }
        //echo 0 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
        // averiguo si ya existe el trigger0
        trigger0 = iio_context_find_device(ctx, "trigger0");
        if (!trigger0) {
            printf("El trigger0 no existía.\n");
            // intento crearlo
            ret = iio_device_attr_write_bool(iio_sysfs_trigger, "add_trigger", false);
            if (ret < 0) {
                printf("No se pudo escribir el atributo add_trigger en iio_sysfs_trigger\n");
            }
            goto free_ctx_dev;
        }

       //cat /sys/bus/iio/devices/trigger0/name > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
        ret = iio_device_set_trigger(dev, trigger0);
        if (ret < 0) {
            printf("No se pudo setear el trigger\n");
            goto free_ctx_dev;
        }
    }
    // inicializamos los punteros a los canales
    chn0 = iio_device_get_channel(dev, ANALOG_IN0);
    chn1 = iio_device_get_channel(dev, ANALOG_IN1);
    chn2 = iio_device_get_channel(dev, ANALOG_IN2);
    chn3 = iio_device_get_channel(dev, ANALOG_IN3);
    chn_NOTUSED1 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED1);
    chn_NOTUSED2 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED2);
    chn_NOTUSED3 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED3);
    chn_NOTUSED4 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED4);
    chn_NOTUSED5 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED5);
    chn_NOTUSED6 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED6);
    chn_NOTUSED7 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED7);

   // deshabilitar todos los canales, se habilitarán de a uno para accederlos.
    iio_channel_enable(chn0);
    iio_channel_enable(chn1);
    iio_channel_enable(chn2);
    iio_channel_enable(chn3);
    iio_channel_disable(chn_NOTUSED1);
    iio_channel_disable(chn_NOTUSED2);
    iio_channel_disable(chn_NOTUSED3);
    iio_channel_disable(chn_NOTUSED4);
    iio_channel_disable(chn_NOTUSED5);
    iio_channel_disable(chn_NOTUSED6);
    iio_channel_disable(chn_NOTUSED7);

    iio_buf = iio_device_create_buffer(dev, 16, false);
    if (!iio_buf) {
        char buf_test[256];
        iio_strerror(-(int)errno, buf_test, sizeof(buf_test));
        printf("Create buffer failed : %s\n", buf_test);
        goto free_ctx_dev;
    }
    return true;

// liberar contexto y device
free_ctx_dev:
    iio_context_destroy(ctx);
    ctx = NULL;
    dev = NULL;
    return false;
}

bool ADC_read(void)
{
    struct iio_channel *chn;
    timeval_t     tiempo;
    ssize_t nbytes_rx;
    char *p_dat, *p_end;
    ptrdiff_t p_inc;

   // tomo el tiempo
    gettimeofday(&tiempo, NULL);

   //canales Refill
    nbytes_rx = iio_buffer_refill(iio_buf);
    if (nbytes_rx < 0) {
        printf("Error refilling buf %d\n", (int) nbytes_rx);
    }

   // READ: Get pointers to RX buf and read IQ from RX buf port 0
    p_inc = iio_buffer_step(iio_buf);
    p_end = iio_buffer_end(iio_buf);

    for (p_dat = (char*) iio_buffer_first(iio_buf, chn); p_dat < p_end;
            p_dat += p_inc) {
        printf("%02x, %02x, %02x, %02x,", ((int16_t*) p_dat)[0], ((int16_t*) p_dat)[1], ((int16_t*) p_dat)[2], ((int16_t*) p_dat)[3]);
    }
    printf("\n");

   return true;
}

int main(int argc, char **argv) 
{
   // inicialización de conversor
   ADC_init();

   while (true) 
  {
        ADC_read();
   }    //cierro while infinito
} /* main() */

After trying different approaches, we came up to this solution. In the main, we initialized the ADC and then in a while(true) in the same main, we call ADC_leer_canal(ch) for each channel of interest. In that function, we enable the channel passed as argument, then if the buffer was previously created, we delete it, and then create a new one, then read the samples and finally disable the channel. After that, we call the same function but with next channel and so on. This is the only approach we managed to get it working. This approach is far from what we would like it to be. Keep in mind that we need the samples of the four channels to be in the same buffer in order to acquire them simultaneously.

I attached the code below:

//defines 
#define ANALOG_IN0            0
#define ANALOG_IN1            1
#define ANALOG_IN_NOTUSED1    2
#define ANALOG_IN_NOTUSED2    3
#define ANALOG_IN_NOTUSED3    4
#define ANALOG_IN_NOTUSED4    5
#define ANALOG_IN_NOTUSED5    6
#define ANALOG_IN_NOTUSED6    7
#define ANALOG_IN2            8
#define ANALOG_IN3            9
#define ANALOG_IN_NOTUSED7    10

//global variables
struct iio_context* ctx;
struct iio_device* dev;
struct iio_buffer* iio_buf;
struct iio_channel *chn0;
struct iio_channel *chn1;
struct iio_channel *chn2;
struct iio_channel *chn3;
struct iio_channel *chn_NOTUSED1;
struct iio_channel *chn_NOTUSED2;
struct iio_channel *chn_NOTUSED3;
struct iio_channel *chn_NOTUSED4;
struct iio_channel *chn_NOTUSED5;
struct iio_channel *chn_NOTUSED6;
struct iio_channel *chn_NOTUSED7;

bool ADC_init(void)
{
    ssize_t ret;
    struct iio_device *iio_sysfs_trigger;
    struct iio_device *trigger0;
    const struct iio_device *current_trigger;
    ctx = iio_create_local_context();
    dev = iio_context_get_device(ctx, DEVICE_LOCAL);

   //pregunto si ya esta seteado el current_trigger en el device "dev"
    ret = iio_device_get_trigger(dev, &current_trigger);
    if (ret < 0) {
        char buf_test[256];
        iio_strerror(-(int) ret, buf_test, sizeof(buf_test));
        printf("Error al obtener el current trigger : %s\n", buf_test);
    }

   //si no tiene seteado ningun trigger
    if (!current_trigger)
    {
        // obtengo puntero al sysfs_trigger
        iio_sysfs_trigger = iio_context_find_device(ctx, "iio_sysfs_trigger");
        if (!iio_sysfs_trigger) {
            printf("Trigger %s not found\n", "iio_sysfs_trigger");
            goto free_ctx_dev;
        }
        //echo 0 > /sys/bus/iio/devices/iio_sysfs_trigger/add_trigger
        // averiguo si ya existe el trigger0
        trigger0 = iio_context_find_device(ctx, "trigger0");
        if (!trigger0) {
            printf("El trigger0 no existía.\n");
            // intento crearlo
            ret = iio_device_attr_write_bool(iio_sysfs_trigger, "add_trigger", false);
            if (ret < 0) {
                printf("No se pudo escribir el atributo add_trigger en iio_sysfs_trigger\n");
            }
            goto free_ctx_dev;
        }

       //cat /sys/bus/iio/devices/trigger0/name > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
        ret = iio_device_set_trigger(dev, trigger0);
        if (ret < 0) {
            printf("No se pudo setear el trigger\n");
            goto free_ctx_dev;
        }
    }
    // inicializamos los punteros a los canales
    chn0 = iio_device_get_channel(dev, ANALOG_IN0);
    chn1 = iio_device_get_channel(dev, ANALOG_IN1);
    chn2 = iio_device_get_channel(dev, ANALOG_IN2);
    chn3 = iio_device_get_channel(dev, ANALOG_IN3);
    chn_NOTUSED1 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED1);
    chn_NOTUSED2 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED2);
    chn_NOTUSED3 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED3);
    chn_NOTUSED4 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED4);
    chn_NOTUSED5 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED5);
    chn_NOTUSED6 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED6);
    chn_NOTUSED7 = iio_device_get_channel(dev, ANALOG_IN_NOTUSED7);

   // deshabilitar todos los canales, se habilitarán de a uno para accederlos.
    iio_channel_disable(chn0);
    iio_channel_disable(chn1);
    iio_channel_disable(chn2);
    iio_channel_disable(chn3);
    iio_channel_disable(chn_NOTUSED1);
    iio_channel_disable(chn_NOTUSED2);
    iio_channel_disable(chn_NOTUSED3);
    iio_channel_disable(chn_NOTUSED4);
    iio_channel_disable(chn_NOTUSED5);
    iio_channel_disable(chn_NOTUSED6);
    iio_channel_disable(chn_NOTUSED7);

   // TODO: agregar etiquetas de salida para liberar recursos en casos de error
    return true;

// liberar contexto y device
free_ctx_dev:
    iio_context_destroy(ctx);
    ctx = NULL;
    dev = NULL;
    return false;
}

bool ADC_leer_canal(uint8_t canal){
    // debemos crear un buffer que nos permita almacenar una determinada
    // cantidad de muestras de los 4 canales activos, 2 bytes por cada canal.
    struct iio_channel *chn;
    timeval_t     tiempo;
    ssize_t nbytes_rx;
    char *p_dat, *p_end;
    ptrdiff_t p_inc;

   // averiguo el canal
    switch(canal){
    case ANALOG_IN0:    chn = chn0; break;
    case ANALOG_IN1:    chn = chn1; break;
    case ANALOG_IN2:    chn = chn2; break;
    case ANALOG_IN3:    chn = chn3; break;
    default :            chn = NULL;
    }
    iio_channel_enable(chn);

   if (iio_buf) {
        iio_buffer_destroy(iio_buf);
        iio_buf = NULL;
    }
    iio_buf = iio_device_create_buffer(dev, 16, false);
    if (!iio_buf) {
        char buf_test[256];
        iio_strerror(-(int)errno, buf_test, sizeof(buf_test));
        printf("Create buffer failed : %s\n", buf_test);
        return false;
    }

   // tomo el tiempo
    gettimeofday(&tiempo, NULL);

   //canales Refill
    nbytes_rx = iio_buffer_refill(iio_buf);
    if (nbytes_rx < 0) {
        printf("Error refilling buf %d\n", (int) nbytes_rx);
    }

   // READ: Get pointers to RX buf and read IQ from RX buf port 0
    p_inc = iio_buffer_step(iio_buf);
    p_end = iio_buffer_end(iio_buf);
    printf("\n%lu.%lu Leídos en buffer refill: %d canal %d\n"
            , tiempo.tv_sec
            , tiempo.tv_usec
            , nbytes_rx
            , canal
            );
    for (p_dat = (char*) iio_buffer_first(iio_buf, chn); p_dat < p_end;
            p_dat += p_inc) {
        printf("%02x,", ((int16_t*) p_dat)[0]);
    }
    iio_channel_disable(chn);
    printf("\n");

   return true;
}

int main(int argc, char **argv) 
{
   // inicialización de conversor
   ADC_init();

   while (true) 
  {
        ADC_leer_canal(ANALOG_IN0);
        ADC_leer_canal(ANALOG_IN1);
        ADC_leer_canal(ANALOG_IN2);
        ADC_leer_canal(ANALOG_IN3);
   }    //cierro while infinito
} /* main() */

Looking forward a soon response and a possible best solution.

Thanks in advanced daniel_m.

Regards,
Martin

Hi @martin_mig,

I believe that this error is hardware related - apparently the iMX6ULL does not support multi-channel ADC conversion.

In the iMX6ULL Applications Processor Reference Manual (which can be downloaded at the NXP website, requires sign-in) at page 393 the ADC diagram shows that the input channels are selected by a MUX and that the ADC1_HC0 register controls it.

At page 418 it says the input channel to be converted can be selected by writing to the Input Channel Select (ADCH) field.

So, although it is not explicitly said that the ADC does not support multi-channel conversion, it shows that only a single channel must be chosen in order to perform the conversion.

This is further corroborated by the fact that the Data result register (ADC1_R0) result is dependent of the chosen input channel in ADC1_HC0, as said in page 419.

But there is an alternative: the iMX6ULL features two ADCs that share the same pins (see Colibri iMX6ULL datasheet, page 54). You can try to use one input channel of each ADC simultaneously to improve performance. See if libiio detects them both by using iio_context_get_devices_count() with the context you’ve created at the beginning of your code.

Best regards,
André Morishita

Hi @andre_m.tx ,

The “iio_context_get_devices_count()” throws only 2 devices:
*- iio:device0: 2198000.adc (buffer capable).
*- iio_sysfs_trigger.

Following is all the places where the adc is referenced in our device-tree.

  1. imx6UL.dtsi:
  	adc1: adc@2198000 {
  		compatible = "fsl,imx6ul-adc", "fsl,vf610-adc";
  		reg = <0x02198000 0x4000>;
  		interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
  		clocks = <&clks IMX6UL_CLK_ADC1>;
  		num-channels = <2>;
  		clock-names = "adc";
  		fsl,adck-max-frequency = <30000000>, <40000000>,
  					 <20000000>;
  		status = "disabled";
  	};
  1. imx6ULL-colibri:
  &adc1 {
    	num-channels = <10>;
    	vref-supply = <&reg_module_3v3_avdd>;
    	pinctrl-names = "default";
    	pinctrl-0 = <&pinctrl_adc1>;
  };

  &iomuxc {
	imx6ull-colibri {
    	pinctrl_adc1: adc1grp {
  	  	fsl,pins = <
  		  	MX6UL_PAD_GPIO1_IO00__GPIO1_IO00        0x3000 /* SODIMM 8 */
  		  	MX6UL_PAD_GPIO1_IO01__GPIO1_IO01        0x3000 /* SODIMM 6 */
  		  	MX6UL_PAD_GPIO1_IO08__GPIO1_IO08        0x3000 /* SODIMM 4 */
  		  	MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x3000 /* SODIMM 2 */
  	  	>;
    	};
    	//continue with more pinctrl...
	}
   }
  1. imx6ULL-colibri-eval-V3.dtsi
  &adc1 {
	status = "okay";
  };

This is all we got. Maybe there is a way to add the 2nd adc to our device tree, but we don´t know exactly how to do that.
If there is a way to achieve the alternative you suggested or if someone has already try it and succeeded, please share it with us so we can tested.

Another alternative is this one, where its says:

When a multiplexer changes how an iio device behaves (for example
by feeding different signals to an ADC), this driver can be used
to create one virtual iio channel for each multiplexer state.

From our understanding, the idea is to multiplex the channels of the adc but at kernel level, so that each channel would have its own trigger and therefore its own buffer, in order to perform a “nearly simultaneous” converstion of the four channels. We don’t even tried this one yet. If someone has done it, please share it with us.

Thanks in advanced for the response.

Regards,
Martin

Hi @martin_mig,

Thank you for the detailed response.

Complementing my last post, there is this community thread where it is confirmed that the hardware does not support multi-channel ADC conversion. Furthermore, the Invalid argument error you got is due to the function iio_validate_scan_mask_onehot used by the vf610 driver, which is used by the adc1 device tree node.

You can try adding an adc2 device tree node to your dtsi/dts files as described here. Just make sure to use different pins than the ones used by adc1.

About that multiplexer kernel patch you mentioned, I’m internally discussing it, and once I get a conclusion I will reach you as soon as possible.

Best regards,
André Morishita