In my last blog post I showed how to transfer IoT data over the network using the M2MQTT library. But how, you may ask, do you acquire the data in the first place? Well, chances are you have some kind of sensor, be it temperature, pressure, accelerometer or what have you. These sensors, more than likely, interface to the IoT node processor via some kind of bus. It could be I2C, SPI, CAN, LIN or any number of others in the alphabet soup. By far the two most popular low level sensor buses are I2C and SPI. I2C stands for Inter-Integrated Circuit and was invented by Philips Semiconductor (now NXP Semiconductor), SPI stand for Serial Peripheral Interface invented by Motorola.
In this example I will show how we can interface to these buses using managed code (C#) in Visual Studio 2013. I will use the BeagleBone Black as the platform running Windows Embedded Compact 2013. A prebuilt demo image is available at my Codeplex site along with all the source code for the example.
To start I wired a prototype A/D board I acquired from Adafruit Industries (by the way a great place for the DIY electronics enthusiast) to a Beaglebone prototyping board. This module uses a Texas Instruments ADS1015 12bit A/D converter and uses the I2C bus as its interface. The BeagleBone connectors breakout all the needed interface signals and supply voltages needed to connect up the sensor. I am using a pair of pressure transducers connected to two channels on the A/D converter module. I won’t go into the full hardware details as this posting is focusing on the software interface. Here is a picture of the setup:
The BSP for the BeagleBone comes with low level drivers for both the I2C and SPI buses available on the AM335X processor, which the Beaglebone incorporates. These drivers are written in native “C” code and implement a “stream driver” as discussed quite a bit on this site. The driver does the low level work talking to the I2C controller on the AM335X part. Because this driver is implemented as a stream driver it has a well defined interface set much like our standard file I/O calls. Things like XXX_Init, XXX_Read, XXX_Write and XXX_IOCTRL are all very familiar calls we see again and again. As you might have assumed the XXX_Read and XXX_Write calls will be used to actually read and write data but how do you do things that are I2C bus specific, for example setting the slave device’s bus address? Well this is where the XXX_IOCTRL function comes into play.
So how do we get at these interfaces in managed code? Well the traditional way is to P/Invoke the WIN32 API file I/O calls. We need to do this because the managed interfaces supplied in the Compact Framework are just not rich enough (for example no XXX_IOCTRL). Now comes the tricky part, I would normally just include a third party managed assembly, like the OpenNETCF library, which has great utility and already implements a stream driver wrapper. Unfortunately, as you may have heard, we loose binary compatibly with Windows Embedded Compact 2013 and we can not use older managed assemblies for the most part. This is because the managed to native low level calls are marshaled differently in WEC2013 and this stings us when using P/Invoke type calls.
Not to worry though, we can recompile the source against the new Compact Framework 3.9 and for the most part everything should work as it did under CF 3.5 and before as the basic WIN32 API have not really changed. This is what I had to do as the first exercise. Now, equipped with a fine managed stream driver interface, we can move on to the task at hand, interfacing and talking on the I2C bus.
I created an I2CSensorApp in Visual Studio 2013, added the newly recompiled OpenNETCF assembly reference and proceeded to add the I2C class:
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using OpenNETCF.IO;
namespace Embedded101.I2C
{
public enum I2CPort : uint
I2C0 = 1,
I2C1,
I2C2
}
public class I2C : StreamInterfaceDriver
public enum SubAddressMode
MODE_0,
MODE_8,
MODE_16,
MODE_24,
MODE_32
public enum Speed
SPEED100KHZ,
SPEED400KHZ,
SPEED1P6MHZ,
SPEED2P4MHZ,
SPEED3P2MHZ
#region I2C device IOCTL codes
private const Int32 CODE_IOCTL_I2C_SET_SLAVE_ADDRESS = 0x0200;
private const Int32 CODE_IOCTL_I2C_SET_SUBADDRESS_MODE = 0x0201;
private const Int32 CODE_IOCTL_I2C_SET_BAUD_INDEX = 0x0202;
private const Int32 FILE_DEVICE_UNKNOWN = 0x00000022;
private const Int32 FILE_ANY_ACCESS = 0x0;
private const Int32 METHOD_BUFFERED = 0x0;
private const Int32 IOCTL_I2C_SET_SLAVE_ADDRESS =
((FILE_DEVICE_UNKNOWN) <<
16
) | ((FILE_ANY_ACCESS) << 14)
| ((CODE_IOCTL_I2C_SET_SLAVE_ADDRESS) << 2) | (METHOD_BUFFERED);
private const Int32 IOCTL_I2C_SET_SUBADDRESS_MODE =
((FILE_DEVICE_UNKNOWN) << 16) | ((FILE_ANY_ACCESS) << 14)
| ((CODE_IOCTL_I2C_SET_SUBADDRESS_MODE) << 2) | (METHOD_BUFFERED);
private const Int32 IOCTL_I2C_SET_BAUD_INDEX =
| ((CODE_IOCTL_I2C_SET_BAUD_INDEX) << 2) | (METHOD_BUFFERED);
#endregion
#region ctor / dtor
/// <summary>
/// Provides access to the I2C bus on the OMAP.
/// </
summary
>
public I2C(I2CPort port) : base("I2C" + Convert.ToString((uint)port) + ":")
// open the driver
Open(FileAccess.ReadWrite, FileShare.ReadWrite);
~I2C()
// close the driver
Close();
public void SetSlaveAddress(UInt16 slaveAddress)
try
UInt32 SA = (UInt32)slaveAddress;
this.DeviceIoControl(IOCTL_I2C_SET_SLAVE_ADDRESS, SerializeToByteArray(SA));
catch (Exception)
throw new Exception("Unable to complete SetSlaveAddress DeviceIoControl:" + Marshal.GetLastWin32Error());
public void SetSubAddressMode(SubAddressMode subAddressMode)
UInt32 SAM = (UInt32)subAddressMode;
this.DeviceIoControl(IOCTL_I2C_SET_SUBADDRESS_MODE, SerializeToByteArray(SAM));
throw new Exception("Unable to complete SetSubAddressMode DeviceIoControl:" + Marshal.GetLastWin32Error());
public void SetBaudRate(Speed speed)
UInt32 SPD = (UInt32)speed;
this.DeviceIoControl(IOCTL_I2C_SET_BAUD_INDEX, SerializeToByteArray(SPD));
throw new Exception("Unable to complete SetBaudRate DeviceIoControl:" + Marshal.GetLastWin32Error());
public int Write(byte register, byte value)
base.Seek((int)register, SeekOrigin.Current);
return base.Write(SerializeToByteArray(value));
public int Write(byte register, UInt16 value)
public int Write(byte register, UInt32 value)
public int Write(byte register, byte data)
return base.Write(data);
public byte ReadByte(byte register)
return (byte)DeserializeFromByteArray(base.Read(sizeof(byte)),typeof(byte));
public Int32 ReadInt24(byte register)
return (Int32)DeserializeFromByteArray24(base.Read(3), typeof(Int32));
public Int16 ReadInt16(byte register)
return (Int16)DeserializeFromByteArray(base.Read(sizeof(Int16)), typeof(Int16));
public UInt32 ReadUInt32(byte register)
return (UInt32)DeserializeFromByteArray(base.Read(sizeof(UInt32)), typeof(UInt32));
public void Read(byte register, ref byte data)
data = base.Read(data.Length);
#region P/Invoke helpers
/// <
/// Byte array serializer
param
name
=
"anything"
></
returns
private static byte SerializeToByteArray(object anything)
int rawsize = Marshal.SizeOf(anything);
IntPtr buffer = Marshal.AllocHGlobal(rawsize);
Marshal.StructureToPtr(anything, buffer, false);
byte rawdatas = new byte[rawsize];
Marshal.Copy(buffer, rawdatas, 0, rawsize);
Marshal.FreeHGlobal(buffer);
return rawdatas;
/// De-serializer from byte array
"rawdatas"
"anytype"
private static object DeserializeFromByteArray(byte rawdatas, Type anytype)
int rawsize = Marshal.SizeOf(anytype);
if (rawsize > rawdatas.Length)
return null;
Marshal.Copy(rawdatas, 0, buffer, rawsize);
object retobj = Marshal.PtrToStructure(buffer, anytype);
return retobj;
private static object DeserializeFromByteArray24(byte rawdatas, Type anytype)
Marshal.Copy(rawdatas, 0, buffer, rawdatas.Length);
Next comes the transducer class. This class will setup and configure the registers on the A/D converter using its slave address and the I2C bus specific instance.
using System.Linq;
using System.Threading;
public static class Xducer
static I2C XducerBus;
static Int32 Xducer1Offset = 0;
static Int32 Xducer2Offset = 0;
#region ctor /dtor
/// Access to the pressure xducers on I2C bus
public static void Init()
XducerBus = new I2C(I2CPort.I2C1);
XducerBus.SetBaudRate(I2C.Speed.SPEED400KHZ);
XducerBus.SetSlaveAddress(0x48);
XducerBus.SetSubAddressMode(I2C.SubAddressMode.MODE_8);
XducerBus.Write(0x02, unchecked((UInt16)0x00028000)); // lo thresh register
XducerBus.Write(0x03, unchecked((UInt16)0x00037fff)); // hi thresh register
XducerBus.Write(0x01, unchecked((UInt16)0x00010480));
XducerBus.SetSubAddressMode(I2C.SubAddressMode.MODE_0);
XducerBus.Write(0x48, 0); // results register
#region Raw xducer readings
public static Int32 Xducer1Raw
get
byte data = new byte[2];
byte config = new byte[3];
config[0] = 0x01; // point to configuration register
config[1] = 0x34; // mux channel (AN2/AN3 = Xducer1)
config[2] = 0x80;
XducerBus.Write(0x48, config); // config registers
XducerBus.Write(0x48, 0);
XducerBus.Read(0x48, ref data);
Thread.Sleep(10);
data[0] ^= 0x80;
return (256 * data[0] + data[1]);
public static Int32 Xducer2Raw
config[1] = 0x04; // mux channel (AN0/AN1 = Xducer2)
/// Returns O2 pressure in mmHg x 10
public static Int32 Xducer1Reading
double scale;
scale = 0.128 * (double)(Xducer1Raw - 32768 + Xducer1Offset);
return (Int32)scale;
/// Returns scaled xducer reading
public static Int32 Xducer2Reading
scale = 0.128 * (double)(Xducer2Raw - 32768 + Xducer2Offset);
public static void ZeroXducers()
Xducer1Offset = -(Xducer1Raw - 32768);
Xducer2Offset = -(Xducer2Raw - 32768);
And finally, the form displaying the sensor reading:
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using Embedded101.I2C;
namespace I2CSensorApp
public partial class Form1 : Form
public Form1()
InitializeComponent();
Xducer.Init();
tmrUPDATE.Interval = 500;
tmrUPDATE.Enabled = true;
private void tmrUPDATE_Tick(object sender, EventArgs e)
lblXDUCER1READING.Text = Xducer.Xducer1Reading.ToString() + " mmHg";
lblXDUCER2READING.Text = Xducer.Xducer2Reading.ToString() + " mmHg";
You can see the initializing call Xducer.Init() which will instantiate the I2C bus and then configure its parameters by setting its bus speed and slave address. It then initializes the A/D converter by writing to some of its configuration registers. From here on we can read and display the transducer values. A read operation will automatically trigger an A/D convert cycle as well as a scaling operation to present the values in meaningful units. In this case pressure in millimeters of mercury (mmHg).
So we have come full circle. Now, with the reading local in memory we can send the data out to the world using our IoT transport of choice. Which loops us back to my previous post.
Re: Windows Embedded Compact 2013 on Beaglebone gets IoT sample
Please contact me regarding a full installation of this BSP for WINCE2013, and what is necessary for...
-- Aaron Peterson
Re: BeagleBone BSP code clean up
Hi David, Interest in the fully version of the image. Please let me know the commercials. I need clarification...
-- CB
Re: BeagleBone BSP gets several improvements
Hi David, How can I get a full version (without reboot) of your image available for demonstration?
-- Marco Aurélio Braun
Re: Low power operation on the community IoT Beaglebone BSP (Part 1)
Hello , dvescovi . Because of Job , I use your Beaglebone black BSP which is helpful for me . I want...
-- KevinHsu
Re: Yet Another Gotcha: Compact 7 Update
Hi David, Would you please contact me regarding the wince7 BSP for the BBB? I have a few LCD Capes...
-- trialsrideraz
Seems MS may have posed a new updated ISO on MSDN. For those without a subscription, you are still out...
-- dvescovi
Re: Power management on the Beaglebone part 2–Battery
Hi David, First of all thanks for this, blog it's been very useful. Before I go ahead and solder anything...
-- Juanes1220
Re: More improvements for Beaglebone BSP
Thank you for the fixes. The latest version builds under WEC7! Yay! Next .. have to try deploying it...
-- OzFlipper
Of course, I meant the WEC2013 SDK ... see what happens when you work with too many bits at the same...
Hi David. First, thank you for what must have been a huge amount of work. I have been trying to install...