Using Ambient Light Sensor interrupts in Linux

This article deals with Ambient Light Sensor interrupts from userspace application point of view.

APDS9306 Ambient light sensors is used as examples in this article but the concepts are applicable to other devices as well.

Ambient light sensors, pressure sensors, temperature sensors and many other devices belong to the IIO subsystem in Linux kernel.

Below is an useful link to application developers:

https://wiki.st.com/stm32mpu/wiki/How_to_use_the_IIO_user_space_interface

Please follow the previous blog post on how to use apds9306 here.

What are interrupts?

As the name suggests, these are events which can happen synchronously or asynchronously, interrupting the normal program flow of the processor or waking up the processor if it is in sleep state.

Interrupts require triggers, every interrupt is triggered by something. Not going into too much details, broadly, interrupts are classified into two categories – software interrupts and hardware interrupts.

Hardware interrupts can be internal to an SoC where an IP block interrupts the CPU or external interrupts where some device on your board has dedicated interrupt line(s) sending interrupt signals to your SoC.

Why is it useful ?

An application in userspace can take a decisions based on the intensity of light detected by a sensor in few different ways, one is to have an application thread read the sensor values periodically through the sysfs interface and take decisions, this approach might look simple but there are a few sequence of events which happen under the hood. Every userpace read of a sysfs attribute results in more than one context switches, the device driver for your sensor sends a I2C read request to your light sensor, the sensor then replies back with the ADC values which is copied to the userspace application.

In the polling based approach, typically the following sequence occurs:

  1. Application reads the sensor value through sysfs
  2. Application compares the read value with predefined threshold value
  3. Loop through steps 1 and 2 till the read value reaches the predefined threshold value
  4. Take decision if value crosses threshold
  5. Continue or abort program

The above approach is the easiest to develop and implement but not good for power consumption and CPU usage. Battery operated devices has to be extra careful as simple implementations like the above for several devices on your board add up to a lot in the end.

The other approach is to use interrupts (events) where userspace applications sets certain threshold values in the device itself, enable interrupt feature and use poll() or select() group of APIs in userspace to read a specific device file in /dev directory. Whenever the conditions are met, the device wakes up your application thread.

In Interrupt driven approach, typically the following sequence occurs:

  1. Application sets the threshold value to the device through sysfs
  2. Application enables interrupt in sysfs
  3. Application opens a file in /dev and waits for reads to come back from this blocking call. Usually, this is done using another thread.

In the above approach, the application thread is put to sleep and does not get scheduled until a hardware interrupt is sent by the device, the CPU virtually does no work for your application till an interrupt is received, no frequent reading of values, no sending I2C requests.

Using APDS9306 in interrupt mode

IIO tools are explained in this blog.

There is a handy little userspace application called iio_event_monitor which can be used to monitor events in userspace from IIO devices. IIO events gets triggered when the device sends an interrupt.

Compiling iio_event_monitor

Linux kernel build also has few userspace tools which can be used as boilerplate code and validating subsystem and driver functionalities. Steps below builds tools for IIO subsystem.

The example below uses OpenSTLinux ecosystem, toolchain and STM32MP157C-DK2 board.

Steps to build the Linux kernel can be found here.

Source your cross-compile toolchain:

PC $> source toolchain_mickledore-mpu-v24.06.26/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi

Navigate to your cross-compiled Linux kernel source or build directory:

PC $> cd kernel_mickledore-mpu-v24.06.26/stm32mp1-openstlinux-6.1-yocto-mickledore-mpu-v24.06.26/sources/arm-ostl-linux-gnueabi/linux-stm32mp-6.1.82-stm32mp-r2-r0/linux-6.1.82

Build userspace IIO tools:

PC $> make -C tools iio O="../build"

There are two binaries which are copied to the target device:

PC $> scp ../build/iio/lsiio root@192.168.7.1:~/
PC $> scp ../build/iio/iio_event_monitor root@192.168.7.1:~/
Demonstration

There are three SSH sessions required for this little demo on your target board:

Verify the path of the light sensor in 1st session:

SSH1 root@stm32mp1:~# cat /sys/bus/iio/devices/iio:device0/name 
apds9306

Start catting the raw values in the 1st session, this shows the current reading from the sensor (this is just for reference) :

SSH1 root@stm32mp1:~# while true; do cat /sys/bus/iio/devices/iio:device0/in_illuminance_raw; sleep 1; done 
262143
262143

Start listening for events in 2nd session (this is you blocking thread):

SSH2 root@stm32mp1:~# ./iio_event_monitor /dev/iio:device0

List all the attributes available in the events directory of apds9306 in SSH3:

SSH3 root@stm32mp1:~# ls -1 /sys/bus/iio/devices/iio:device0/events/
in_illuminance_thresh_either_en
in_intensity_clear_thresh_either_en
thresh_adaptive_either_en
thresh_adaptive_either_value
thresh_adaptive_either_values_available
thresh_either_period
thresh_either_period_available
thresh_falling_value
thresh_rising_value

To use the event feature, we have to write a threshold value to the sensor and enable interrupts. There are several possible interrupt settings such as Adaptive, Period, Rising threshold and Falling threshold.

For this example we will use Falling threshold as it is easier for demonstration to just cover the sensor and block the light falling on it. We write a value to thresh_falling_value which is significantly lower than the value we are getting in SSH1 session.

SSH3 root@stm32mp1:~# echo 100000 > thresh_falling_value

Enable threshold interrupts:

SSH3 root@stm32mp1:~# echo 1 > in_illuminance_thresh_either_en

If the incident light to the sensor is obstructed, we notice messages in SSH2 session:

SSH2 root@stm32mp1:~# ./iio_event_monitor /dev/iio:device0 
Event: time: 1677864452169204036, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452271299119, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452373307661, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452475270411, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452577339494, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452679387244, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452781402994, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452883412328, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864452985561828, type: illuminance, channel: 0, evtype: thresh, direction: either
Event: time: 1677864453087488578, type: illuminance, channel: 0, evtype: thresh, direction: either

Here is a video of the test:

Ambient Light Sensor interrupt in Linux using APDS9306

Leave a Reply

Your email address will not be published. Required fields are marked *

31 − 24 =