Haskell, Raspberry Pi and I2C

Posted on January 3, 2017

The code: https://github.com/flounders/MLXi2C

After having my Raspberry Pi for a little over a year, I wanted to finally make use of the GPIO pins on it. So I purchased a Melexis MLX90614 contact-less temperature sensor. Cautiously I looked over the data sheet to see how to connect it to the Pi. After some initial confusion, my dad straightened me out since I had everything wired backwards. Had I powered it on, I probably would have fried it. $15 up in smoke. This article details what you can do with this sensor using Haskell when you don’t fry it.

After getting the wiring right my next problem was I didn’t know how to access it through code. It took a few minutes but I found this code sample written in C: https://www.raspberrypi.org/forums/viewtopic.php?t=17738&p=243946#p362243. The code checked out and my temperature sensor was now working. Since I learned Haskell, I have a pretty strong distaste for using C (it’s incredibly tedious to write code that has no undefined behavior), so I decided to see what I could do with my sensor in Haskell.

The C library that makes it possible to use the Raspberry Pi’s GPIO pins is BCM2835. In order to make this work for Haskell we need to use that library through the HPi module which you can find on hackage. At the time of this writing version 5.0.1 is broken, so please use 5.0.0 for your projects until it gets updated. Initially I had issues linking against the C library with my Haskell code, you can fix this by adding extra-libraries: bcm2835 to your library or executable in cabal. You can check my GitHub repository to see where I fit it in. Without that line, GHC will do nothing but give you linker errors. Obviously you will need the BCM2835 library installed too. If you are compiling on the Pi, it’s likely going to be in the distribution’s repositories; otherwise you can download and build it.

To make use of the library our Haskell code will need at least these three modules:

import qualified Data.ByteString as B
import Data.Word
import System.RaspberryPi.GPIO

My code has two additional modules, but this is the basics for what you need. ByteStrings are what HPi uses for reads and writes. We will need to unpack the ByteStrings with unpack which returns a list of Word8.

In order to read data from the sensor we need to set the baud rate, for the MLX90614 this is 25000. setI2cBaudRate 25000 takes care of this for us. To read data from the sensor we have to do a repeated start. I’m not sure how many I2C devices require this, but this sensor does. writeReadRSI2C takes care of repeated restarts; It’s first argument is the address you are writing to (for this sensor we are using 0x5a), our next argument is what we are writing and for this sensor that is the RAM address we want to read from (6 for ambient temperature, 7 for object temperature or infrared sensor), and then the number of bytes we want back. At first I only took 2 bytes back, but on repeated reads the program would complain about illegal operations, so I went to using 3 bytes as the linked C code does and now the program can take repeated samples. Our temperature is now back from the sensor in a ByteString.

In order to convert this data to something useful we need to get the order of our bytes straight. The manual for my sensor says that it sends the least significant byte first on a read. To make conversion as simple as possible I just reverse the two bytes I’m going to use. While we need to read all three bytes to prevent an illegal operation, we don’t have to use all three. I just take the first two, unpack them and reverse. Our bytes at this point are in Word8 which means the total integer value is seperated into two bytes that represent only values from 0 to 256. To make it one whole integer I wrote this function bytesToInteger = foldl (\acc x -> fromIntegral x + acc * 16^2) 0. What this function does is take our list of Word8 and preserves place value by multiplying each increase in place value by 256. If you have trouble understanding this have a look at Positional systems on Wikipedia or look at this appendix to the IntermezzOS tutorial. After we have converted our sequence of bytes into an integer we then divide by 50 to get a temperature in Kelvin. To convert to Celsius simply subtract 273.15 from the temperature in Kelvin.

You can find my code here.