Testing the Proximity and Ambient Light Sensors

The Librem 5 phone has a proximity sensor (PS) and ambient light sensor (ALS) provided by the VCNL4040M3OE-H5 module. These sensors can be accessed programmatically via messages sent to the relevant addresses on the appropriate I2C bus. It can be useful to test that these sensors are working before using the appropriate system D-Bus interface.

This guide provides a basic overview of the commands that can be used to obtain ambient light and proximity information from the device using the I2C bus.

Using the I2C Bus

The VCNL4040M3OE-H5 module is accessed via an I2C bus address and exposes a set of registers that can be accessed by their addresses in the device. This means that we need two addresses – a bus address and a device address – to access a register.

The kernel provides abstractions that allow the sensors to be accessed fairly simply without requiring knowledge of the message protocol used for I2C devices.

Preparations

Before trying to access the sensors as the purism user, you need to install the i2c-tools package on the development board:

sudo apt install i2c-tools

Then add the purism user to the i2c group:

sudo usermod -a -G i2c purism

This user should now have permission to read and write the device files for the I2C devices and run the tools to access them.

Accessing the Module

The module is available via address 0x60 on I2C bus 1. This can be verified by running the i2cdetect tool:

purism@pureos:~$ /usr/sbin/i2cdetect -y 1

This should produce output like the following:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: 60 -- -- -- -- -- -- -- -- -- UU -- -- -- -- --
70: -- -- -- -- -- -- -- --

It should be clear that there is a device at address 0x60 on the I2C bus – this should be the proximity and ambient light sensor.

Reading the Device ID

We check that the device at address 0x60 on I2C bus 1 is the sensor by querying its ID_L and ID_M registers, which should be readable at address 0x0c on the device. We can do this with the i2cget tool:

/usr/sbin/i2cget -y 1 0x60 0x0c w

The expected value is 0x0186 for the sensor module. If a different value is returned then the device is not the one we were expecting.

Powering the Ambient Light Sensor

Initially, the device will be powered down. To determine its state we read its ALS_CONF register at address 0x00 on the device:

/usr/sbin/i2cget -y 1 0x60 0x00 w

This should return 0x0001, indicating that the shut down bit is enabled. Set it to 0x0000 with the i2cset tool:

/usr/sbin/i2cset -y 1 0x60 0x00 0 w

When we no longer need to read from the device, we set the shut down bit again:

/usr/sbin/i2cset -y 1 0x60 0x00 1 w

We can read the state of the device again to verify that it has powered down, if necessary.

Reading the Ambient Light Level

With the device powered, ambient light sensor readings can be obtained by reading from the ALS_Data_L and ALS_Data_H registers at address 0x09 on the device:

/usr/sbin/i2cget -y 1 0x60 0x09 w

White channel values can be read from the White_Data_L and White_Data_H registers at 0x0a on the device:

/usr/sbin/i2cget -y 1 0x60 0x0a w

These should vary as the ambient light level at the sensor changes.

Powering the Proximity Sensor

The state of the proximity sensor can be obtained by reading the PS_CONF1 and PS_CONF2 registers at address 0x03 on the device:

/usr/sbin/i2cget -y 1 0x60 0x03 w

The lowest bit (PS_SD) of the value returned is 1 if the sensor is shut down. Set it to 0 to power it up. In the simplest case, we can set both the registers to 0x00:

/usr/sbin/i2cset -y 1 0x60 0x03 0 w

Setting the lowest bit to 1 again will shut down the sensor.

Reading Proximity Data

With the proximity sensor enabled, values can be obtained for the proximity of object to the sensor by reading from the PS_Data_L and PS_Data_M registers at address 0x08 on the device:

/usr/sbin/i2cget -y 1 0x60 0x08 w

Closer objects should result in higher values, up to the maximum value set in bit 3 (PS_HD) of the PS_CONF2 register. The default resolution is 12 bits.

Using Python to Access the Sensors

The python3-smbus package can be used to read these levels. Install it on the development board Python script with apt:

sudo apt install python3-smbus

The following script will show the ambient light levels every second until it is terminated: ambient.py

#!/usr/bin/env python3

import smbus
import time

# Bus number and i2c address:
BUS = 1
ADDRESS = 0x60

# Register addresses:
ALS_CONF = 0x00
ALS_Data = 0x09
White_Data = 0x0a

# Masks and values:
ALS_SD = 1

# Create an object to represent the appropriate bus.
bus = smbus.SMBus(BUS)

# Power up the ambient light sensor if not already powered.
state = bus.read_word_data(ADDRESS, ALS_CONF)
if state & ALS_SD == ALS_SD:
    bus.write_word_data(ADDRESS, ALS_CONF, state ^ ALS_SD)

try:
    while True:
        print('Ambient:', bus.read_word_data(ADDRESS, ALS_Data))
        print('White:  ', bus.read_word_data(ADDRESS, White_Data))
        time.sleep(1)
finally:
    # Restore the initial state.
    bus.write_word_data(ADDRESS, ALS_CONF, state)

Similarly, this script will show the proximity sensor’s response to close objects four times a second: proximity.py

#!/usr/bin/env python3

import smbus
import time

# Bus number and i2c address:
BUS = 1
ADDRESS = 0x60

# Register addresses:
PS_CONF1 = 0x03
PS_Data = 0x08

# Masks and values:
PS_SD = 1

# Create an object to represent the appropriate bus.
bus = smbus.SMBus(BUS)

# Power up the proximity sensor if not already powered.
state = bus.read_word_data(ADDRESS, PS_CONF1)
if state & PS_SD == PS_SD:
    bus.write_word_data(ADDRESS, PS_CONF1, state ^ PS_SD)

try:
    while True:
        print('Proximity:', bus.read_word_data(ADDRESS, PS_Data))
        time.sleep(0.25)
finally:
    # Restore the initial state.
    bus.write_word_data(ADDRESS, PS_CONF1, state)