Android Sensor Integration Part 2: Sensor Readings

In the second part of our four part series on Android Sensor Integration we will have a look at how to actually get readings from our SRF02 Ultrasonic Range finder. If you haven’t done so go check part one for the basics of the Android sensor stack and kernel module integration. Otherwise jump right in!

Disclaimer: The integration described here was implemented for the Pandaboard ES Rev B3 with a Linaro Android 4.4.4 and the ultrasonic range sensor SRF02 connected on an I2C bus. The full source code is available on Github. However this is written as a guideline for any standard sensor integration into Android. It should be considered work in progress with several ugly details that I know have to be fixed. And they will be in the future – probably. Only the files I really had to change have been uploaded.

Entries for the sysfs – control the ranging

Finally we can start to implement sensor readings. We’ll begin by creating a file in sysfs as an interface to the userspace. To interact with the sensor we need two access functions: The first one for writing access to enable or disable the readings and the other one for reading access as a way to get the results.

Let’s take a look at the function which handles writing access to the corresponding sysfs entry. As mentioned this function will allow us to enable or disable readings. To do so we read from the standard input. For better evaluation the input is converted to a long. If the detected input value is greater than 0, a delayed workqueue for performing the repeated measurements is created. If it isn’t the existing queue is destroyed to end this periodic action. What exactly is happening here will be discussed in the next chapter.

With srf02_get_values_cyclic () we implement a function to read the results from our file after reading range data from our sensor.

To connect the file name in the sysfs with these access functions and set up a proper rights management, we define a device attribute with name, rights, getter- and setter-functions. The name, value_now, appears in the sysfs as a file we can interact with.

For each file we create for access, a special role of the driver must be registered in the attribute structure.

From a debugging console we can now test enabling and disabling the readings with
echo 1 > /sys/bus/i2c/devices/i2c-4/4-0070/value_now. This command writes a “1” to the sysfs entry we have named above. At this path, i2c-4 means our device is connected at the i2c bus number 4. The folder 4-0070 holds all accessible files for the device at i2c bus 4 with the address 0x70.

Queues and Input events – performing the readings

So why are we choosing delayed workqueues? We want to have a mechanism for the readings that runs periodically in a defined interval without blocking the CPU in between two cycles. Delayed workqueues provide just that. We only have to define a function for them which defines the task that should be repeated.

The delayed workqueue is created, as shown in the code snipped above, by setting up a queue with  create_workqueue(). Then we allocate storage for the work structure and initialize the delayed work with the structure and the function to perform using INIT_DELAYED_WORK(). There is a difference between the functions for setting up and destroying workqueues and delayed workqueues, it won’t work if you mix them.

At the end we writeour work structure ( my_work_t *work) to the created queue ( workqueue_struct my_wq), which contains a reference to the work function ( workq_fn) and set a delay for starting this task. With the same function, the work function writes itself back into the queue before it ends.

The work function is responsible for the reading itself. Therefore we file our device as an I2C slave and start the activity by sending the command for getting the results in centimeters to the command register of the client. The ranging of the SRF02 ultrasonic sensor takes up to 66 milliseconds, so just to be sure we will wait 100 ms. Then we are able to read the value from registers 2 and 3 as high and low bytes.

Above we initialized the input subsystem we’ll use to generate input events for the HAL driver. Here in the workq_fn function we finally produce these events.

Reporting a new input event is easy: With the input_event() function we specify the device that generates the event, name the type of the event as well as the event code and transfer the result of the ranging as the value of the event. For a proximity sensor we use EV_ABS as event type which is intended for describing absolute events and ABS_DISTANCE  as event code that is used to characterize a distance between the sensor and its interaction surface. With the input_sync()  function we tell the receiver that this report is complete. To test whether input events with our label are generated, we run adb getevent.

With the input subsystem we already have a mechanism to receive the results, but e.g for debugging we implement the function for reading access to the sysfs. That means we are able to read out sensor’s results with cat /sys/bus/i2c/devices/i2c-4/4-0070/value_now.

In the kernel module we just take the result and place it in the buffer that was given as an argument. A value less than 0 means that reading is disabled.

Android tired, Sensor needs to sleep

Because Android is a mobile platform, saving energy is quite important, so the system switches to a suspended state as often as possible. Our kernel module will support this behavior by stopping the readings while sleeping. To support the Android autosleep mechanism we need to expand the srf02_priv structure with an entry for an early_suspend  structure. This handler is filled in the probe() function and holds references to the functions that will be called for suspend and resume. Then we register the handler with register_early_suspend() .

The functions for suspend and resume themselves are very simple for the proximity sensor because it is not necessary to save any state of the device. So the idea is to cancel the workqueue and with it the measurement when the device goes into suspend mode. And when resuming we recreate the queue with our worker function to perform the ranging as if the suspension had never happened.

Clean up

Now we are nearly done with the kernel module for the SRF02 sensor and will just take a short look at the clean up process. Everything we create or register in the module_init() method must be destroyed or unregistered in the module_exit() method in reversed order to avoid memory leaks. That’s the same pattern the jump marks in the init-method itself work, if anything fails there. In the same way the srf02_i2c_remove() function cleans up everything generated in the probe() function.

Stay tuned

Half way through! In the next two parts (due next week) we will get to know HAL and how it interacts with our kernel module and the framework. Until then head over to our website to learn more about embedded development and the IoT.

Sources (Parts 1 and 2)

The Whole Story

comments powered by Disqus