Monday, January 23, 2017

Nusbio I2C

Overview

The Inter-Integrated Circuit (I²C, i2c) is a multi-master, multi-slave serial communication protocol invented by Philips Semiconductor (now NXP Semiconductors), primarily used in embedded systems.
A lot devices like an EEPROM, LCD, GPIO expander, LED drivers and more support the I2c protocol.

It is generally not available for Windows easily, though it is available on the Raspberry PI via Python and C.

That is why I created Nusbio.net in 2015 that offers in a simple way to talk I2C from Windows and .NET

 

A lot of devices designed by Adafruit use the I2C protocol and are compatibles with Nusbio and any .NET languages.

C# Class


This post explains the I2CEngine .NET class for the Nusbio device.

Wiring

Since Nusbio has 8 gpio pins, you can technically setup 4 independent I2C buses. But generally one is enough.
I use Gpio0 for SCL and Gpio1 for SDA as convention. Obviously you can connect multiple I2C devices to the bus.
Nusbio does not come with any pull up resistors so it is up you to take care of it.
Generally if you use an I2C breakout it is part of the breakout.

Software

For each I2C device on the bus you must create an object of the class I2CEngine. After that you call the WriteBuffer() method to initiate an I2C write operation and the method ReadBuffer for a I2C read operation.
The methods takes care of the I2C Control byte. You do not have to pass it as part of the buffer.
The methods return true if the operation succeeded. For the Readxxxx methods, if the operation succeeded you can then read the buffer which will be updated with the data.


public I2CEngine(Nusbio nusbio, NusbioGpio sdaOutPin, NusbioGpio sclPin, byte deviceId);
public bool ReadBuffer(int len, byte[] data);
public bool WriteBuffer(byte[] buffer);
public bool WriteBuffer(byte address8bit, byte[] buffer);


Samples


I2C EEPROM


Here is a basic sample reading the first 64 bytes of the I2C EEPROM 24LC256 which is a 32k bytes EEPROM with a page size of 64 byte.

byte EEPROM1_WR = 80; // 0xA0;
                                
var i2c = new I2CEngine(nusbio, NusbioGpio.Gpio1, NusbioGpio.Gpio0, EEPROM1_WR);
var addr = 0;
var buffer = new byte[64];
if (i2c.WriteBuffer(new byte[2] { (byte)(addr >> 8), (byte)(addr & 0xFF) }))
{
  var r = i2c.ReadBuffer(64, buffer);
}

We do offer specific classes to handle I2C and SPI EEPROM, see our folder EEPROM on github.


I2C MCP9808 Temperature Sensor

Here part of our class MCP9808 _Temperature Sensor.cs available on Github.
The method Begin will return true if the device is detected on the bus else false.

public bool Begin(byte deviceAddress = MCP9808_I2CADDR_DEFAULT)
{
    try
    {
        this._i2c.DeviceId = deviceAddress;
        if (read16(MCP9808_REG_MANUF_ID) != MCP9808_REG_MANUF_ID_ANSWER) return false;
        if (read16(MCP9808_REG_DEVICE_ID) != MCP9808_REG_DEVICE_ID_ANSWER) return false;
        return true;
    }
    catch (System.Exception ex)
    {
        System.Diagnostics.Trace.WriteLine(ex.ToString());
        return false;
    }
}

public double GetTemperature(TemperatureType type = TemperatureType.Celsius)
{
    uint16_t t = read16(MCP9808_REG_AMBIENT_TEMP);
    double temp = t & 0x0FFF;
    temp /= 16.0;
    if ((t & 0x1000) == 0x1000) temp -= 256;
    switch (type)
    {
        case TemperatureType.Celsius: return temp;
        case TemperatureType.Fahrenheit: return CelsiusToFahrenheit(temp);
        case TemperatureType.Kelvin: return temp*CELCIUS_TO_KELVIN;
        default:
            throw new ArgumentException();
    }
}

private UInt16 read16(uint8_t reg)
{
    UInt16 value = 0;

    if (this._i2c.WriteBuffer(new byte[1] { reg }))
    {
        var buffer = new byte[2];
        this._i2c.ReadBuffer(2, buffer);
        value = (System.UInt16)((buffer[0] << 8) + buffer[1]);
    }
    else throw new ArgumentException();    
}


We do offer specific classes to handle I2C and SPI EEPROM, see our folder EEPROM on github.


Advanced Methods

To improve transfer performance, the class I2CEngine expose the following methods which optimize the number of USB operations to execute the I2C operations (we combine an I2C Read + I2C Write operation in one USB transaction)

public bool Send16BitAddressAnd1Byte(int address16bit, byte b);
public bool Send16BitsAddressAndBuffer(int address16bit, int len, byte[] buffer);
public int  Send16BitsAddressAndRead1Byte(short address8bit);
public bool Send16BitsAddressAndReadBuffer(int address16bit, int len, byte[] data);

public bool Send1ByteCommand(byte command);
public bool Send2BytesCommand(byte command0, byte command1);
public bool Send3BytesCommand(byte command0, byte command1, byte command2);

public int Send1ByteRead1Byte(byte address8bits);
public _2BytesOrInt16Result Send1ByteRead2Bytes(byte cmd);        



Performance

Using the EEPROM 24LC256 which is an I2C 32k bytes with a max clock of 400 kHz, we can transfer the 32 k byte of data from the EEPROM to the PC at the rate of 15 k bytes per second in batch mode of 64 pages at a time.

Transferring one page a the time give a transfer rate of 8 k bytes per second.

Output or input batch mode is necessary to increase performance for all I2C devices.

Transfer speed may vary also depending on the speed of the computer.

 

Sunday, January 15, 2017

$0.5 2K EEPROM M93C86 for .NET, CSharp and Nusbio.net

I experimented with a cheap EEPROM the M93C86 2k byte of data for $0.5 programmed in C# with Nusbio.net (M93C86 datasheet).

The EEPROM used the SPI protocol but its API is not as straightforward as the Microchip EEPROMs.
After a day of work it seems that we can only write one byte at the time and for that we need to pass 4 bytes, of protocol. So write speed is very slow.
It does not seems that there is a concept of page per say, but since I want to re use a C# base class, I set up a page to 256 bytes, and the read performance in SPI out of the box is 10 K byte/S with Nusbio v1.
Since it is just an experiment I will not optimize the code to get 28 K byte/S like with the Microchip SPI EEPROM.
As a reminder Nusbio v 2 can transfer at the rate of 1 to 3 M byte/S in SPI and 100 K byte/S in I2C in
any .NET language. Nusbio v 2 is in prototype mode as 2017/01.


/*
   Copyright (C) 2015, 2016, 2017 MadeInTheUSB LLC
   Written by FT for MadeInTheUSB

   MIT License (MIT)
*/

using System;
using System.Collections.Generic;

namespace MadeInTheUSB.EEPROM
{
    /// 
    /// m93c86 is a 2k byte spi cheap eeprom
    /// http://www.mouser.com/ds/2/389/m93c46-w-955034.pdf
    ///     CS(PD-1k)[] [] VCC
    ///     SCK      [] [] Not used
    ///     MOSI     [] [] ORG Leave unconnected for 16kbit orf
    ///     MISO     [] [] GND
    /// 
    public class EEPROM_M93C86 : EEPROM_25AAXXX_BASE
    {

#if NUSBIO2
        public EEPROM_M93C86() : base(16)
        {
        }
#else
        public EEPROM_M93C86(Nusbio nusbio, 
            NusbioGpio clockPin, 
            NusbioGpio mosiPin, 
            NusbioGpio misoPin, 
            NusbioGpio selectPin,
            bool debug = false) : base(nusbio, clockPin, mosiPin, misoPin, selectPin, 16, 
                debug, 
                chipSelectActiveLow:false // << important
                )
        {
            var b = this.MaxByte;
            var p = this.MaxPage;
            this.SetWriteRegisterEnable();
            //this.SetWriteRegisterDisable();
        }
#endif

    
        public override bool Is3BytesAddress
        {
            get { return false; }
        }

        public override int PAGE_SIZE
        {
            get{ return 256; }
        }

        protected override bool SetWriteRegisterEnable()
        {
            var r = this.SpiTransfer( new List(){ 0x98 /*0b10011000*/, 00 } );
            return r.Succeeded;
        }

        protected override bool SetWriteRegisterDisable()
        {
            var r = this.SpiTransfer(new List() { 0x80 /*0b10000000*/, 00 });
            return r.Succeeded;
        }
        /// 
        /// http://www.mouser.com/ds/2/389/m93c46-w-955034.pdf
        /// based on the data sheet in ORG: 8 byte (low)
        /// writing one byte require 22 Clock Cycle
        /// 1 start bit, 2 bit-opCode, 11-Addr == 2 Clock cycle
        /// We send 24 bits the first 2 bits are 00
        /// I do not think that this EEPROM support write in bulk mode
        /// It does support Read in bulk
        /// 
        /// 
        /// 
        /// 
        public virtual bool WritePage(int addr, byte[] buffer)
        {
            int dt1, dt2, ans;

            for (var num = 0; num < buffer.Length; num++)
            {
                //dt1 = 0b00101000 | ((adrs & 0b0000011100000000) >> 8) ;
                //dt2 = (adrs & 0b0000000011111111) ;
                dt1           = 0x28 | ((addr & 0x700) >> 8);
                dt2           = (addr & 0xFF);
                var spiBuffer = new List() { (byte)dt1, (byte)dt2, buffer[num] };
                var writeR    = this.SpiTransfer(spiBuffer);
                var readR     = this.SpiTransfer(new List() { 0 });
                ans           = readR.Buffer[0];
                addr += 1;
            }
            return true;
        }

        public override EEPROM_BUFFER ReadPage(int addr, int len = -1)
        {
            if (len == -1)
                len = PAGE_SIZE;

            var eb = new EEPROM_BUFFER();
            int dt1, dt2;

            //dt1 = 0b00110000 | ((adrs & 0b0000011100000000) >> 8) ;
            //dt2 = (adrs & 0b0000000011111111) ;
            dt1 = 0x30 | ((addr & 0x700) >> 8);
            dt2 = (addr & 0xFF);

            var spiBufferWrite = new List() { (byte)dt1, (byte)dt2 };
            var spiBufferRead = GetEepromApiDataBuffer(len);
            var buffer = new List();
            buffer.AddRange(spiBufferWrite);
            buffer.AddRange(spiBufferRead);

            var r = this.SpiTransfer(buffer);

            if (r.Succeeded)
            {
                eb.Succeeded = true;
                eb.Buffer = r.Buffer.GetRange(spiBufferWrite.Count, r.Buffer.Count - spiBufferWrite.Count).ToArray();
            }
            return eb;
        }
      
    }
}