Notice:
This post is older than 5 years – the content might be outdated.
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!
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
static ssize_t srf02_store_values_cyclic (struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { unsigned long value; int ret; value = simple_strtoul (buf, NULL, 10); if (value > 0) { dev_static = dev; my_wq = create_workqueue("my_workqueue"); if (my_wq) { work = (my_work_t *) kmalloc (sizeof (my_work_t), GFP_KERNEL); if (work) { INIT_DELAYED_WORK ((struct delayed_work *) work, workq_fn); ret = queue_delayed_work(my_wq, (struct delayed_work *) work, msecs_to_jiffies(100)); } } value_nonstop = 0; } if (value == 0) { cancel_delayed_work((struct delayed_work *)work); value_nonstop = -1; // for disabling - } return size; } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
static DEVICE_ATTR (value_now, 0644, srf02_get_values_cyclic, srf02_store_values_cyclic); static const struct attribute *srf02_attrs[] = { &dev_attr_value_now.attr, &dev_attr_srf02value.attr, NULL, }; static const struct attribute_group srf02_attr_group = { .attrs = srf02_attrs, }; |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
void workq_fn (struct delayed_work *work) { int ret = 0; s32 i2cRet = 0; int value_reg1 = 0; int value_reg2 = 0; struct i2c_client *client = to_i2c_client(dev_static); //write to command register that result shall be in cm i2cRet = i2c_smbus_write_byte_data (client, CMD_COMMAND_REG, CMD_RESULT_IN_CM); msleep(100); //Reading result value_reg1 = i2c_smbus_read_byte_data (client, CMD_RANGE_HIGH_BYTE); value_reg2 = i2c_smbus_read_byte_data (client, CMD_RANGE_LOW_BYTE); value_nonstop = (value_reg1 * 256) + value_reg2; input_event(srf02_input_dev, EV_ABS, ABS_DISTANCE, value_nonstop); input_sync(srf02_input_dev); queue_delayed_work(my_wq, (struct delayed_work *)work, msecs_to_jiffies(100)); } |
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.
1 2 3 4 5 6 7 |
static ssize_t srf02_get_values_cyclic (struct device *dev, struct device_attribute *attr, char *buf) { return sprintf (buf, "%d \n", value_nonstop); } |
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() .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
struct srf02_priv { struct i2c_client *client; #ifdef CONFIG_EARLYSUSPEND struct early_suspend es_handler; #endif }; static int srf02_i2c_probe (struct i2c_client *client, const struct i2c_device_id *id) { // ... #ifdef CONFIG_EARLYSUSPEND srf02_p->es_handler.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; srf02_p->es_handler.suspend = srf02_early_suspend; srf02_p->es_handler.resume = srf02_later_resume; srf02_p->es_handler.data = (void *)client; register_early_suspend(&srf02_p->es_handler); #endif // ... } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#ifdef CONFIG_HAS_EARLYSUSPEND static void srf02_early_suspend (struct early_suspend *suspend) { struct srf02_priv *srf02_p; if (suspend->data) { srf02_p = i2c_get_clientdata((struct i2c_client *) suspend->data); cancel_delayed_work ((struct delayed_work *)work); } } static void srf02_later_resume (struct early_suspend *suspend) { struct srf02_priv *srf02_p; if (suspend->data) { srf02_p = i2c_get_clientdata ((struct i2c_client *) suspend->data); my_wq = create_workqueue("my_workqueue"); if (my_wq) { work = (my_work_t *) kmalloc (sizeof (my_work_t), GFP_KERNEL); if (work) { INIT_DELAYED_WORK ((struct delayed_work *) work, workq_fn); queue_delayed_work(my_wq, (struct delayed_work *) work, msecs_to_jiffies(100)); } } } } #else static void srf02_early_suspend (struct early_suspend *suspend) { } static void srf02_later_resume (struct early_suspend *suspend) { } #endif |
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)
- https://source.android.com/devices/sensors/index.html
- http://www.makelinux.net/ldd3/
- https://www.kernel.org/doc/Documentation/input/input-programming.txt
- https://gitlab.inovex.de/inovex-embedded-marsboard/marsboard-lcd/raw/master/Master_Thesis_Dahmen_299580.pdf
- http://developer.android.com/guide/components/services.html
- http://www.tldp.org/LDP/lkmpg/2.4/html/
4 Kommentare