Pages

Sunday, June 8, 2014

Implementing a device driver for the MPL3115a on the Raspberry Pi

This is a quick device driver for the raspberry pi I made to experiment with the XTRINSIC MEMS Sensor board that contains 3 sensors - A temperature and barometer sensor (using the MPL3115A2 chip), a 3-axis accelerometer, and the digital compass sensor.


I'm currently interested to get the temperature and barometric pressure data for the moment, I would have to write the other drivers later. So this driver is named mpl3115 from the temperature/barometer sensor on the board. Note that for the altimeter/barometer sensor can only work in single mode - either as a barometer or an altimeter, not both. To switch modes, we would have to change the contents of the config registers of the MPL3115 to work. A kernel attribute is provided so that the altimeter/barometer mode can be changed.

Also note that the device driver implementation does not use the 10bit I2C addressing, and I made no effort to touch the repeated start feature of the device. All access to the device is done via i2c smbus read/write byte operations.

There was already a working user mode device driver for the MPL3115, but as you know user mode device drivers can not handle hardware interrupts, thus the rationale for creating this device driver. The device driver works by handling the interrupt lines (GPIO17 on the raspberry pi header) from the sensor and reading them only when there is available data (sensor raises data ready interrupt).

The driver exposes 3 kernel attributes to userland in /sys. Once the driver is loaded, it exposes the following attributes on /sys/bus/i2c/drivers/mpl3115/1-0060 directory:

altbarmode - The current mode of the driver whether it uses Barometric pressure (B) measurement or Altitude (A). By default the driver starts with Barometric pressure mode.
altbarvalue - The last update time from the driver (time barometer/alti- meter was updated) and the altimeter/barometric pressure value, depending on the mode. The format of this is given in the example below:
<hours:min:sec:micro sec>|<altimeter/barometer value>

Example below shows how readings are taken and to switch modes:
pi@raspberrypi:~$ cat /sys/bus/i2c/drivers/mpl3115/1-0060/altbarmode
B
pi@raspberrypi:~$ cat /sys/bus/i2c/drivers/mpl3115/1-0060/altbarvalue
10:39:8:107541|6426032
pi@raspberrypi:~$ echo 'A' > /sys/bus/i2c/drivers/mpl3115/1-0060/altbarmode
pi@raspberrypi:~$ cat /sys/bus/i2c/drivers/mpl3115/1-0060/altbarvalue
10:39:49:688672|19632
pi@raspberrypi:~$
Notice the difference in reading altimeter mode and barometer mode above. Also note that the value is the raw readings of the sensor, user applications must convert this value to meters/feet/etc.
tempvalue - The value of the temperature sensor. This value is a 16 bit value containing the numbers part in the MSB, and the decimal part in the LSB. Since we are prohibited from using floating point in kernel code, user applications must convert this value to Fahrenheit using the formula:
   
    double fahrenheit = 0.0;
    double MSB = (value >> 8) & 0xFF;
    double LSB = value & 0xFF;

    LSB = (LSB > 99) (LSB / 1000) : (LSB / 100);

    fahrenheit = MSB + LSB;
So for the readings below when examining the value of the tempvalue:

pi@raspberrypi:~$ cat /sys/bus/i2c/drivers/mpl3115/1-0060/tempvalue
10:57:2:914445|7856
pi@raspberrypi:~$
7856 is 0x1EB0 (MSB = 1E or 30, LSB = B0 or ), to get the reading in fahrenheit
C = 30.176 ( That's how hot Singapore is! )
If you put your finger on the MPL3115 chip, notice the temperature increase.
Right now only the last read values are taken by this driver, theres currently no kernel FIFO to store all the values when an interrupt is triggered, what was last ready will be reflected. In the future I will expand this driver to store all read values from the sensor in a kernel fifo (or circular buffer) and implements a char driver.

Source code for this driver is in github - https://github.com/vpcola/drivers/tree/master/mpl3115a

Update: Tried to review the code last night and found a bunch of bugs with the code. The values can't be right. All along I thought that the MSB in the temperature readings were in degrees Fahrenheit, but its not the case - its actually in Celsius. There was also a bug in calculating the decimal part of the temperature. I was looking at the Python implementation that was included in the distribution of the Xtrinsic board and found this:

        def getTemp(self):
                t = self.readTemp()
                t_m = (t >> 8) & 0xff;
                t_l = t & 0xff;

                if (t_l > 99):
                        t_l = t_l / 1000.0
                else:
                        t_l = t_l / 100.0
                return (t_m + t_l)

However the official specs of the mpl3115a states that:

This means there is a bug in the python code in the original release.

So the decimal part is actually in steps of 256. That means whatever value in the LSB, you would need to divide this by 256 to get the decimal value part of the temperature. I have corrected the previous driver and added a KFIFO that can be accessed using the /proc directory. This is detailed in my next blog.

No comments: