Use of the AT91 ADC driver


This is the description of the Atmel AT91 ADC driver. This driver uses the Industrial Input/Output (IIO) subsystem.

Documentation for this Linux subsystem is available in the kernel source code directory: drivers/staging/iio/Documentation/.
You can have a look at this documentation inline: overview.txt.


Get the Linux kernel

The driver has been included in the Linux kernel 3.5 release, and support the AT91SAM9G20-EK, AT91SAM9M10G45 and AT91SAM9x5 boards at that time.

The first step is to get the Linux kernel:

tar xvjf linux-3.5.tar.bz2
cd linux-3.5

Configure and Build the Linux kernel

Firstly, use your default kernel configuration:

make ARCH=arm at91sam9xxxx_defconfig

You can enable the AT91 IIO ADC driver by enabling the symbol CONFIG_AT91_ADC or using the Linux kernel configuration interface:

make ARCH=arm menuconfig
Device Drivers  --->
   <*>   Industrial I/O support  --->
      Analog to digital converters  --->
         <M> Atmel AT91 ADC

   -*-   Enable buffer support within IIO
   <*>     Industrial I/O buffering based on kfifo
   -*-   Enable triggered sampling support
   (2)     Maximum number of consumers per trigger (NEW)

Pointing hand note that you can also build this device driver into the kernel itself (not as a module).

Build the Linux kernel image:

make ARCH=arm CROSS_COMPILE=<path_to_cross-compiler/cross-compiler-prefix->
path_to_cross-compiler is only needed if it is not in your PATH. Usually cross-compiler-prefix- looks like arm-linux- , arm-elf- or arm-none-linux-gnueabi- .

To produce a kernel image suitable for U-Boot:

mkimage -A arm -O linux -C none -T kernel -a 20008000 -e 20008000 -n linux-2.6 -d arch/arm/boot/zImage uImage.bin

Load ADC driver

If the driver isn't already loaded or directly compiled into your running kernel, you can use it as a module.

Check if your ADC driver is already available by using this command:

# dmesg | grep iio
[    2.450000] iio iio:device0: Resolution used: 10 bits
[    2.450000] iio iio:device0: ADC Touch screen is disabled.

So you can skip this setup about modules.

If the insertion of the module is needed, this driver's name is at91_adc, so you can load it simply using

modprobe at91_adc


insmod ./at91_adc.ko

root@at91sam9g20ek:~# insmod /lib/modules/3.5.0/kernel/drivers/iio/adc/at91_adc.ko 

Loading the module should have created a sysfs directory in /sys/bus/iio/devices/iio:deviceN, N being between 0 and 255. It is most likely to be 0, but you can be sure by looking into the name file in that directory.

root@at91sam9g20ek:~# ls -l /sys/bus/iio/devices/iio\:device0/

drwxr-xr-x    2 root     root            0 Mar 10 02:24 buffer
-r--r--r--    1 root     root         4096 Mar 10 02:24 dev
-r--r--r--    1 root     root         4096 Mar 10 02:24 in_voltage0_raw
-r--r--r--    1 root     root         4096 Mar 10 02:24 in_voltage1_raw
-r--r--r--    1 root     root         4096 Mar 10 02:24 in_voltage2_raw
-r--r--r--    1 root     root         4096 Mar 10 02:24 in_voltage3_raw
-rw-r--r--    1 root     root         4096 Mar 10 02:24 in_voltage_scale
-r--r--r--    1 root     root         4096 Mar 10 02:24 name
drwxr-xr-x    2 root     root            0 Mar 10 02:24 power
drwxr-xr-x    2 root     root            0 Mar 10 02:24 scan_elements
lrwxrwxrwx    1 root     root            0 Mar 10 02:24 subsystem -> ../../../../bus/iio
drwxr-xr-x    2 root     root            0 Mar 10 02:24 trigger
-rw-r--r--    1 root     root         4096 Mar 10 02:24 uevent

If you have devtmpfs, it will also have created an iio:deviceN file under /dev.

Software Triggers

Software triggers are an ADC operating mode where the software starts the conversion.

This feature is exposed by IIO through the following files:

  • in_voltageX_raw: raw value of the channel X of the ADC
  • in_voltage_scale: value you have to multiply in_voltageX_raw with to have a value in microvolts

Reading into in_voltageX_raw will perform a software trigger on the ADC, then block until the conversion is completed, and finally return the value of this conversion.

Please note that conversions are done one channel at a time.

Here is the output on the AT91SAM9 console that shows an ADC measure when a 3V DC power supply is connected between analog ground AGND and ADC input 0 AD0 pins:

root@at91sam9g20ek:~# cat /sys/bus/iio/devices/iio\:device0/in_voltage0_raw 
root@at91sam9g20ek:~# cat /sys/bus/iio/devices/iio\:device0/in_voltage_scale 

We can calculate the result: 948 x 3222 = 3.0V

Hardware triggers

Hardware triggers are an operating mode of the ADC where conversions are no longer triggered by the software but directly by the hardware. For the G20, you have two types of hardware triggers, either timer counters are the trigger for the conversion, either an external pin is used to provide such an input. In either case, the conversion is started on rising edges.

Please note that the setup of the timer counters in themselves is not achieved by this driver, so you will have to do it yourself. However, some dummy example of how to setup the timer-counter-0 is attached to this page.


IIO configuration of hardware triggers is also done through sysfs.

Important folders in the iio:deviceX directory are:

  • buffer
  • trigger
  • scan_elements

The buffer directory contains 3 files:

  • enabled : get and set the state of the buffer
  • length : get and set the length of the buffer.

The trigger directory contains only one file, current_trigger, that reports and get the trigger in use with the driver. It also refers to the triggerX directories in /sys/bus/iio/devices/

Finally, scan_elements exposes 3 files per channel:

  • in_voltageX_en : is this channel enabled?
  • in_voltageX_index : index of this channel in the buffer's chunks
  • in_voltageX_type : How the ADC stores its data. Reading this file should return you a weird looking string. How to interpret it is explained in the Going Further section

It also contains the same information for the timestamp, under the in_timestamp_* files.

How to set it up

Basically, what you should do for launching the hardware triggers is :

Set up the channels in use (you can enable any combination of the channels you want)

# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_en
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage1_en
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage2_en
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage3_en
Set up the trigger we want to use (it must be the name of one of the triggers present in iio/devices).

TIP With device tree, name of the device may change to some form like "fc034000.adc" instead of simply "at91_adc": the name sysfs file is there to inform you

# echo "at91_adc-dev0-external" > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
# echo "fc034000.adc-dev0-continuous" > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
for instance.

Set up the buffer length (Maximum number of data kept in the buffer, if we reach this number, the older data will be dropped)

# echo 100 > /sys/bus/iio/devices/iio:device0/buffer/length
Enable the capture
# echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable

Now, all the captures are exposed in the character device /dev/iio:device0, with the following format

8 bytes 8 bytes
Chan 0 Chan 1 Chan 2 Chan 3 Timestamp
2 bytes 2 bytes 2 bytes 2 bytes

This means that you can poll() or select() on it to wait for data to arrive, and get them by reading the file, just like you would do with any other file.

And to stop the capture, just disable the buffer

# echo 0 > /sys/bus/iio/devices/iio:device0/buffer/enable

Testing the buffer

In the IIO documentation there is a tool that allows to do quick tests on the buffer from the command-line. The source code is located in drivers/staging/iio/Documentation/generic_buffer.c. First, we need to compile it

arm-linux-gnueabi-gcc --static generic_buffer.c -o generic_buffer


<path_to_cross-compiler/cross-compiler-prefix->-gcc --static generic_buffer.c -o generic_buffer

Then copy the generic_buffer program on your board.

Below is an example of how to use this tool :

First, load the driver:

# modprobe at91_adc
Then enable the channels:
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage0_en
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage1_en
# echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_voltage3_en

Finally, the generic_buffer tool does all the "enable" and "disable" actions for you. You will only need to specify the IIO driver and the trigger you want to use (at91_adc and at91_adc-dev0-external respectively or fc034000.adc and fc034000.adc-dev0-external respectively). You can also specify the buffer length to use (2 in this example). The default value is 128.

# ./generic_buffer -n at91_adc -t at91_adc-dev0-external -l 2
iio device number being used is 0
iio trigger number being used is 3
/sys/bus/iio/devices/iio:device0 at91_adc-dev0-external
7.000000 239.000000 71.000000  480890114100
7.000000 238.000000 72.000000  8365721275892
or with a device tree system:

TIP With device tree, name of the device may change to some form like "fc034000.adc" instead of simply "at91_adc": the name sysfs file is there to inform you

# ./generic_buffer -n fc034000.adc -t fc034000.adc-dev0-external -l 2
iio device number being used is 0
iio trigger number being used is 3
/sys/bus/iio/devices/iio:device0 fc034000.adc-dev0-external
7.000000 239.000000 71.000000  480890114100
7.000000 238.000000 72.000000  8365721275892

While running, it displays values that are stored in the buffer. So using the setup above, we have the following trace:

Channel 0 Channel 1 Channel 3 Timestamp
Sample 1 7 239 71 480890114100
Sample 2 7 238 72 8365721275892

Going further

Understanding the in_voltageX_type file

Reading the in_voltageX_type file should return you something like: le:u10/16>>0

  • le represents the endianness, here little endian
  • u is the sign of the value returned. It could be either u (for unsigned) or s (for signed)
  • 10 is the number of relevant bits of information
  • 16 is the actual number of bits used to store the datum
  • 0 is the number of right shifts needed.

Pin Muxing

By default, all the pins required by the ADC are multiplexed as ADC outputs as soon as you enable it. However, you might want to change that. You can do so by modifying the platform_data structure. The definition of this structure resides in the file include/linux/platform_data/at91_adc.h, and the values are filled in the board files (for example arch/arm/mach-at91/board-at91sam9g20ek.c for the SAM9G20-EK board).

The important fields of that structure for the multiplexing are :

  • channels_used: it is a bitmask of the channels available on the board. Every bit is a boolean to use or not the corresponding channel. For example, the SAM9G20-EK board has 4 ADC channels, so the default value is BIT(0) | BIT(1) | BIT(2) | BIT(3). If you just want the two first channels, just put BIT(0) | BIT(1) instead. The pins for the two last channels won't be multiplexed as ADC outputs, and the channels won't be available in the driver.
  • use_external: it is just a boolean. Just set it to true or false whether you want the pin corresponding to the external trigger multiplexed as trigger inputs or not. Of course, if you set it to false here, all the triggers relying on this pin won't be available to the driver.


It seems that I do not have proper accuracy using AT91SAM9G20-EK ADC?

A: The ADC accuracy is given by the Electrical Characteristics chapter in the product datasheet.

It has been observed on the AT91SAM9G20-EK that the ADC reference seems not stable enough. We recommend to remove the 0Ohm R21 resistor and provide an external 3.3V reference voltage source using ADVREF / VREFP (J24 pin 38).

Topic attachments
I Attachment Action Size Date Who Comment
C source code filec atmel_tca.c manage 1.2 K 2011-12-13 - 15:38 FreeElectrons Example on how to setup the timer counters
r28 - 10 Oct 2017 - 12:45:51 - NicolasFerre
Linux & Open Source related information for AT91 Smart ARM Microcontrollers

Copyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.

Linux® is the registered trademark of Linus Torvalds in the U.S. and other countries.

Atmel® and others, are registered trademarks or trademarks of Atmel Corporation or its subsidiaries. This site is powered by the TWiki collaboration platform

ARM® and others are registered trademarks or trademarks of ARM Ltd. Other terms and product names may be trademarks of others.

Ideas, requests, contributions ? Connect to LinksToCommunities page.

Syndicate this siteRSS ATOM