How to use the IIO user space interface

来自百问网嵌入式Linux wiki

How to use the IIO user space interface with a user terminal.

Purpose

This article describes how to use the IIO with a user terminal.

The use cases of the following examples are:

  • analog to digital
  • digital to analog
  • quadrature encoder[1] monitoring

Conversions between an STM32 board and an external device:

  • Basic reads from ADC (for example by polling) or writes to a DAC are performed using sysfs
  • More advanced use cases (with timer triggers and buffers) are performed using sysfs configuration and character devices either directly or with tools
  • Simulation of a quadrature encoder device using GPIOs

Note: Some IIO tools are used in this article. A list of IIO tools is defined in dedicated articles: IIO Linux kernel tools and libiio tools.

How to do a simple conversion using the sysfs interface

The IIO sysfs interface can be used to configure devices and do simple conversions at low rates.

This is usually referred to as IIO direct mode in IIO device drivers.

Documentation/ABI/testing/sysfs-bus-iio[2] is the Linux® kernel documentation that fully describes the IIO standard ABI.

Note: To convert a raw value to standard units, the IIO defines this formula: Scaled value = (raw + offset) * scale

How to do a simple ADC conversion using the sysfs interface

This example shows how to read a single data from the ADC, using sysfs.

Info.png The ADC is enabled by thedevice tree: ADC DT configuration example

First, look for the IIO device matching the ADC peripheral:

$ grep -H "" /sys/bus/iio/devices/*/name | grep adc                            # or use 'lsiio | grep adc'
/sys/bus/iio/devices/iio:device0/name:48003000.adc:adc@0                       # Going to use iio:device0 sysfs, that matches ADC1
/sys/bus/iio/devices/iio:device1/name:48003000.adc:adc@100

Then, perform a single conversion on an ADC, and also read the ADC scale and offset:

$ cd /sys/bus/iio/devices/iio:device0/
$ cat in_voltage6_raw                                                          # Convert ADC1 channel 0 (analog-to-digital): get raw value
40603
$ cat in_voltage_scale                                                         # Read scale
0.044250488
$ cat in_voltage_offset                                                        # Read offset
0
$ awk "BEGIN{printf (\"%d\n\", (40603 + 0) * 0.044250488)}"                    # Scaled value = (raw + offset) * scale
1796                                                                           # Result: 1796 mV

How to do a simple DAC conversion using the sysfs interface

This example shows how to write single data to the DAC, using sysfs.

Info.png The DAC is enabled by the device tree: DAC DT configuration example

First, look for the IIO device matching the DAC peripheral:

$ lsiio | grep dac
Device 003: 40017000.dac:dac@1                                                 # Going to use iio:device3 sysfs, that matches DAC1
Device 004: 40017000.dac:dac@2

Then, check the DAC scale to compute the raw value:

$ cd /sys/bus/iio/devices/iio:device3/
$ cat out_voltage1_scale                                                       # Read scale
0.708007812
$ awk "BEGIN{printf (\"%d\n\", 2000 / 0.708007812)}"                           # Example to convert convert 2000 mV (millivolts)
2824
$ echo 2824 > out_voltage1_raw                                                 # Write raw value to DAC1
$ echo 0 > out_voltage1_powerdown                                              # Enable DAC1 (out of power-down mode): DAC now converts from digital to analog
                                                                               # User can now convert new value with 'echo xxxx > out_voltage1_raw'

Convert one or more channels using triggered buffer mode

Building upon on what is described in the article User space interface, the user should:

  • configure and enable the IIO trigger via sysfs (/sys/bus/iio/devices/triggerX)
  • configure and enable the IIO device via sysfs (/sys/bus/iio/devices/iio:deviceX)
  • access configured events and data from character device (/dev/iio:deviceX)

This is typically the case when using one of the IIO buffer modes.

See The Linux driver implementer’s API guide - Industrial I/O Buffers for further details.

The STM32 provides several hardware triggers, among which TIM and LPTIM can be used in IIO.

How to set up a TIM or LPTIM trigger using the sysfs interface

This example shows how to set up a TIM or an LPTIM trigger, using sysfs.

Info.png TIM and/or LPTIM are enabled by device tree: See TIM configured in PWM mode and trigger source example and/or LPTIM DT configuration as PWM and trigger source example

Runtime configuration is performed using the sysfs interface:

$ lsiio | grep tim                                                             # Look for IIO device that matches TIM and/or LPTIM peripheral
Device 010: 44000000.timer:trigger@0
Trigger 000: tim6_trgo
Trigger 001: tim1_trgo
Trigger 002: tim1_trgo2
Trigger 003: tim1_ch1
Trigger 004: tim1_ch2
Trigger 005: tim1_ch3
Trigger 006: tim1_ch4

Either the TRGO or the PWM output can be configured, and used as the trigger source for analog conversions.

  • To configure the timX_trgo trigger, the "sampling_frequency" (Hz) can be set directly:
$ cd /sys/bus/iio/devices/trigger0/
$ cat name
tim6_trgo
$ echo 10 > sampling_frequency                                                 # Set up 10Hz sampling frequency on tim6_trgo
$ cd /sys/bus/platform/devices/44000000.timer:pwm/pwm/pwmchip0
$ echo 0 > export                                                              # Export tim1_ch1 PWM
$ echo 100000000 > pwm0/period
$ echo 50000000 > pwm0/duty_cycle
$ echo 1 > pwm0/enable                                                         # Enable tim1_ch1 with 10Hz frequency and 50% duty cycle

How to perform multiple ADC conversions in triggered buffer mode

This example shows how to read multiple data from an ADC, to scan one or more channels.

Info.png The ADC is enabled by the device tree: ADC DT configuration example

Conversions are triggered by the TIM or LPTIM hardware trigger, See How to set up a TIM or LPTIM trigger using the sysfs interface.

As an example, ADC in0 and in1 can be converted in sequence.

  • sysfs interface overview:
$ cd /sys/bus/iio/devices/iio\:device0
$ cat name
48003000.adc:adc@0
$ ls scan_elements
in_voltage0_en  in_voltage0_index  in_voltage0_type  in_voltage1_en  in_voltage1_index  in_voltage1_type
$ ls trigger
current_trigger
$ ls buffer
enable  length  watermark
  • Example to enable ADC channel 0 and channel 1, and use the tim6_trgo trigger source :
$ echo 1 > scan_elements/in_voltage0_en                         # Enable channel 0
$ echo 1 > scan_elements/in_voltage1_en                         # Enable channel 1
$ echo "tim6_trgo" > trigger/current_trigger                    # Assign tim6_trgo trigger to ADC
$ cat trigger/current_trigger
tim6_trgo
$ echo 1 > buffer/enable                                        # Start ADC in buffer mode
  • character device data out:
$ hexdump -e '"iio0 :" 8/2 "%04x " "\n"' /dev/iio:device0 &     # Read data from /dev/iio:device0, display by group of 8, 2 bytes.
iio0 :9f15 0000 9e9f 0000 9f18 0000 9ee4 0000                   # Result: raw data out in the form of: in0 data | in1 data | in0 data...
...

How to perform multiple ADC conversions in triggered buffer mode using libiio

Prerequisite: please see the similar example: How to perform multiple ADC conversions in triggered buffer mode.
That example uses iio_readdev[3] provided by libiio tools.

The example below requests 8 data samples on the ADC configured with:

$ iio_readdev -t trigger0 -s 8 -b 8 iio:device0 voltage0 voltage1 | hexdump
0000000 9efe 0000 9ed9 0034 9eff 0000 9ee5 0000
0000010 9edb 0011 9ecc 000b 9eb0 0000 9ed4 0001

How to get ADC analog watchdog events

Before starting triggered conversions (buffer mode), an analog watchdog can be set to report 'out of range' events:

  • it needs to be configured with high and low threshold values
  • it needs to be enabled to report events when either the high or low threshold is reached
$ cd /sys/bus/iio/devices/iio\:device0/
echo $((0x9000)) > events/in_voltage0_thresh_rising_value           # Set high threshold value
echo $((0x7000)) > events/in_voltage0_thresh_falling_value          # Set low threshold value
echo 1 > events/in_voltage0_thresh_either_en                        # Enable analog watchdog to report out of range events

Before starting conversions as described in above, start event monitor in background:

$ iio_event_monitor /dev/iio:device0 &

After starting conversions, if the ADC conversion results are outside the configured thresholds, the event monitor reports as follows:

Event: time: 1529352199639112110, type: voltage, channel: 0, evtype: thresh, direction: either
Event: time: ...

How to use the quadrature encoder with the sysfs interface

This example shows how to monitor the position (count) of a linear (or rotary) encoder.

It uses quadrature the encoder[1] interface available on the TIM and LPTIM internal peripherals.

How to set up the TIM quadrature encoder with the sysfs interface

Info.png The TIM quadrature encoder is enabled by the device tree: TIM configured as quadrature encoder interface

Runtime configuration is performed using the sysfs interface:

$ lsiio | grep tim                             # Look for IIO device that matches TIM peripheral
Device 002: 44000000.timer:timer@0
...
$ cd /sys/bus/iio/devices/iio:device2/
$ cat in_count_quadrature_mode_available       # List available modes:
channel_A channel_B quadrature
$ echo 65535 > in_count0_preset                # set ceiling value (upper limit for the counter)
$ echo quadrature > in_count0_quadrature_mode  # set quadrature mode
$ echo 0 > in_count0_raw                       # reset the counter
$ echo 1 > in_count0_en                        # enable the counter

Once started, the encoder value and direction are available using:

$ cat in_count0_raw
0
$ cat in_count0_count_direction
up

How to set up the LPTIM quadrature encoder with the sysfs interface

Info.png The LPTIM quadrature encoder is enabled by the device tree: LPTIM configured as quadrature encoder interface

Runtime configuration is performed using the sysfs interface:

$ lsiio | grep tim
Device 003: 50021000.timer:counter
...
$ cd /sys/bus/iio/devices/iio:device3/
$ cat in_count_quadrature_mode_available       # List available modes:
non-quadrature quadrature
$ echo 65535 > in_count0_preset                # set ceiling value (upper limit for the counter)
$ echo quadrature > in_count0_quadrature_mode  # set quadrature mode
$ echo both-edges > in_count0_polarity         # set polarity (both edges)
$ echo 1 > in_count0_en                        # enable the counter

Once started, the encoder value is available using:

$ cat in_count0_raw
0

How to use the TIM or LPTIM quadrature encoder with the sysfs interface

This example shows how to monitor the TIM quadrature encoder interface via sysfs (the LPTIM case is very similar):

  • In this example, two GPIO lines (PD1, PG3) are externally connected to the TIM (or LPTIM)
  • Then libgpiod[4] is used to set and clear the encoder input pins, to 'emulate' an external quadrature encoder device.

Step-by-step example:

  • Externally connect and initialise GPIO pins to TIM or LPTIM encoder input pins, to 'emulate' an external quadrature encoder
$ gpiodetect
...
gpiochip6 [GPIOG] (16 lines)
...
gpiochip3 [GPIOD] (16 lines)
...
$ gpioset gpiochip3 1=0                       # initialize PD1 to 0 as GPIO, connect it to TIM or LPTIM channel A input
$ gpioset gpiochip6 3=0                       # initialize PG3 to 0 as GPIO, connect it to TIM or LPTIM channel B input

文件:Quadrature x4 encoding.png

$ cd /sys/bus/iio/devices/iio:deviceX/
$ cat in_count0_raw                           # [channel A, channel B] = [0, 0]
0
$ gpioset gpiochip3 1=1                       # [channel A, channel B] = [1, 0]
$ cat in_count0_raw
1
$ gpioset gpiochip6 3=1                       # [channel A, channel B] = [1, 1]
$ cat in_count0_raw
2
$ gpioset gpiochip3 1=0                       # [channel A, channel B] = [0, 1]
$ cat in_count0_raw
3
$ gpioset gpiochip6 3=0                       # [channel A, channel B] = [0, 0]
$ cat in_count0_raw
4
$ cat in_count0_count_direction
up
$ gpioset gpiochip6 3=1                       # [channel A, channel B] = [0, 1]
$ cat in_count0_raw
3
$ cat in_count0_count_direction               # Direction has changed, down-counting now
down
...

References


<securetransclude src="ProtectedTemplate:PublicationRequestId" params="8796 | 2018-09-25 | PhilipS"></securetransclude>