BBBCSIO

I2CPort Example Code

 

About the I2CPort Class

The I2CPort class writes to and reads from the one of the three I2C bus On Chip Peripheral (OCP) devices integrated into the Beaglebone Black CPU. In the Linux operating system there are two ways of accessing the I2C devices; the first, called I2CDev, is the standard and approved Linux technique. I2CDev is a device driver and if the existence of a I2C port is detected during boot time, the device driver interface will be exposed as a file in the /dev directory of the Beaglebone Black file system. The I2C ports will only be detected if that port is appropriately configured in the Device Tree.

The second type of access is called Memory Mapped access and this treats the Beaglebone Black's RAM memory as if it were a file. The I2C port is manipulated as if it were a bit at an offset in a virtual file. The BBBCSIO library does not, at this time, provide a class which utilizes Memory Mapped access to the I2C port - the file system based I2CDev class named I2CPortFS is the only one available.

There are three I2C ports on a Beaglebone Black. If these ports have been correctly configured in the Device Tree, the I2CDev device driver for that port will be accessible via a file in the /dev directory. This file will have a name in the format i2cdev_<i2cport> where <i2cport> is the number of the I2C port. Thus the file /dev/i2cdev_0 is the interface for the I2CDev device driver on I2C port 0. If you do not see the i2cdev_* file in the /dev directory this means that the I2C device is not correctly configured in the Device Tree. By the way, these files are not the sort of files you can open and write text to in order control the port (like you do with the SYSFS GPIO subsystem). The i2cdev files are true device files and if you want them to do anything you have to use an ioctl() call.

Each device on an I2C bus has its own address called the device address and the Beaglebone Black functions as the I2C bus master. You should use the free i2c-tools package to probe the Beaglebone Black for all available I2C ports and also to probe for the addresses of the devices addresses connected to a particular I2C port. The use of the i2c-tools will not be discussed here - however Derek Molloy has posted a useful video which, in part, covers that subject.

Warnings - READ THIS!!!

The I2C port used in the examples below is I2C2 - this must be configured in the Device Tree in order for it to be available for use.

The armhf Linux running on the Beaglebone Black is not a real-time operating system. However, an OCP peripheral such as the I2CPort has its own internal clock and so, once the information is given to the port, it will be transmitted and received at the configured port speed with no interruptions. However, transfers made via multiple calls to the I2C port can, and will, have variable gaps between transmissions as the process is pre-emptively swapped in and out by the kernel.

An Example of the I2CPort Usage

The code below illustrates how to use the I2CPortFS class to read from an I2C device (an ADC) and then write that value to another I2C device (an 4x7 Segment display) on the same I2C bus.

        /// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=
        /// <summary>
        /// Writes to and reads from an I2C port - I2CDev version. The example code
        /// assumes there are two I2C devices on the bus. A Sparkfun Serial 7 
        /// Segment Display on I2C address 0x71 and an AdaFruit 12-Bit 4 Channel ADC
        /// with Programmable Gain Amplifier on I2C address 0x48.
        /// 
        /// NOTE: 
        ///   The address of the device is specified in the i2cPort.Write or
        ///   i2cPort.Read calls. You do not need to send that as the first outgoing
        ///   byte or set the READ/WRITE bit in that address. The I2C driver does
        ///   that for you automatically.
        /// 
        /// NOTE:
        ///    Make sure you are aware of how I2C buses work and properly
        ///    configure the SCL and SDA lines.
        /// 
        /// NOTE that you can use the i2c-tools package to probe the BBB for all available
        ///   I2C ports and also to probe for the addresses of the devices addresses
        ///   connected to a particular I2C port.
        /// 
        /// </summary>
        /// <param name="i2cID">The i2c portID</param>
        /// <history>
        ///    22 Jan 15  Cynic - Originally written
        /// </history>
        public void I2CPortFSTest(I2CPortEnum i2cID)
        {
            byte convByte;
            int bufLen = 4;
            byte[] txByteBuf = new byte[bufLen];
            byte[] rxByteBuf = new byte[bufLen];

            // open the port
            I2CPortFS i2cPort = new I2CPortFS(i2cID);
            if (i2cPort.PortIsOpen == false)
            {
                throw new Exception("ssHandle == null");
            }

            // read the A0 register from the 4 channel ADS1015 chip on the
            // AdaFruit 12-Bit ADC - 4 Channel with Programmable Gain Amplifier.
            // The codes below are straight out of the example on page 8 of the
            // ADS1015 datasheet. This, by default, sets things up as a differential
            // measurement between A0 and A1 so make sure A1 is grounded. Otherwise
            // change the line txByteBuf[1] value to txByteBuf[1] = 0x44; to make it
            // non- differential

            // Write to the Config Register to set things up
            txByteBuf[0] = 0x01;
            txByteBuf[1] = 0x04; // either ground A1 or change this to 0x44
            txByteBuf[2] = 0x83;
            i2cPort.Write(0x48, txByteBuf, 3);

            // Write to the Pointer Register, so we read from A0 in the next step
            txByteBuf[0] = 0x00;
            i2cPort.Write(0x48, txByteBuf, 1);

            // Read two bytes from the Conversion Register
            rxByteBuf[0] = 0x00;
            rxByteBuf[1] = 0x00;
            i2cPort.Read(0x48, rxByteBuf, 2);

            // we now have two hex bytes which contain the 12 bit value of
            // the voltage on input A0, convert them from twos complement and
            // shift according to the datasheet
            int a0ADCReading = (((int)(256 * rxByteBuf[0]) + (int)rxByteBuf[1])>>4);
            Console.WriteLine("ADC Value on A0=" + a0ADCReading.ToString("x4")); 

            // now populate the tx Register with the hex equivalents of the bytes
            // so we can display them
            txByteBuf[0] = 0x30; // always zero, the ADC is 12 bits
            convByte = (byte)((a0ADCReading & 0x00000f00) >> 8);
            txByteBuf[1] = (byte)(convByte<10 ? convByte + 0x30 : convByte-10 + 0x41);
            convByte = (byte)((a0ADCReading & 0x000000f0) >> 4);
            txByteBuf[2] = (byte)(convByte<10 ? convByte + 0x30 : convByte-10 + 0x41);
            convByte = (byte)((a0ADCReading & 0x0000000f) >> 0);
            txByteBuf[3] = (byte)(convByte<10 ? convByte + 0x30 : convByte-10 + 0x41);

            Console.WriteLine("Display Hex =" + " 0x" + txByteBuf[0].ToString("x2") + " 0x" + txByteBuf[1].ToString("x2") + " 0x" + txByteBuf[2].ToString("x2") + " 0x" + txByteBuf[3].ToString("x2"));
 
            // write to the Sparkfun 7 segment display at I2C address 0x71
            i2cPort.Write(0x71, txByteBuf, 4);

            // close the port
            i2cPort.ClosePort();
            i2cPort.Dispose();
        }
In the above code, the i2cID is passed in when the function was called. This value is a member of the I2CPortEnum class which lists all possible I2C ports which can be present on the Beaglebone Black. The above code is called via a line which looks like:
I2CPortFSTest(I2CPortEnum.I2CPORT_2);