Help with i2c - aka help me learn how to convert a datasheet into code

I’m trying to do some more lower level work than I am used to and need some help getting started.

If any kind soul would help me get going I’d appreciate it.

tl;dr
I’m trying to write a .NET driver for the MAX11616: MAX11616 Low-Power, 4-/8-/12-Channel, I²C, 12-Bit ADCs in Ultra-Small Packages | Analog Devices

http://datasheets.maximintegrated.com/en/ds/MAX11612-MAX11617.pdf

Long version: tl;dr
I put together a quick and simple PCB for this ADC. Now I’m trying to get the code to work with it, but I’ve never worked with I2C directly. My initial coding attempts yield no return data from the first call.

I don’t want someone to write it for me, but to just provide some help in getting going.

If anyone is interested – either via email or here on the forum - I’d be very grateful.

Most of the time the problem is in 7bit vs 8bit address. I would check that first.

I would also recommend grabbing a copy of the source code for one of the standard Gadgeteer I2C Modules like the Accelerometer and looking through that.

2 Likes

The module is MikroBus.NET based, but I would appreciate help to use it in .NETMF so I can use the chip elsewhere. I’m sure the logic is transferable.

This is what I came up thus far:


public ADC9(Hardware.Socket socket)
        {
            adc9 = new MBN_ADC9(socket, 0x035, Enums.ClockRatesI2C.Clock100KHz);

            adc9.SetupADC();
        }


const byte _address = 0x35;


 public ADC9(Hardware.Socket socket, Byte address, ClockRatesI2C clockRateKHz)
        {
            try
            {
                // Checks if needed I²C pins are available. You can add other pins if needed.
                Hardware.CheckPinsI2C(socket);
                _socket = socket;
                    
                // Create the driver's I²C configuration
                _config = new I2CDevice.Configuration(address, (Int32)clockRateKHz);

                //TODO: implement the constructor here
            }
            // Catch only the PinInUse exception, so that program will halt on other exceptions
            // Send it directly to the caller
            catch (PinInUseException) { throw new PinInUseException(); }
        }


 var result = new Byte[1];
            var actions = new I2CDevice.I2CTransaction[2];
            actions[0] = I2CDevice.CreateWriteTransaction(new byte[] { (_address << 1) });
            actions[1] = I2CDevice.CreateReadTransaction(result);

            // Don't forget to use Hardware.I2CBus with the extension method, so that the configuration is correct
            Hardware.I2CBus.Execute(_config, actions, 1000);
            var retval = result[0];

This is an attempt to send the START.

retval had a value of 240; I should only be getting 1 acknowledgement bit in return.

The datasheet says that default address = 0110101; 7 bit as you mentioned.

I was trying to do this first part (in image below).

I’m not sure about the START part, as that appears to be related to state of the SCA/SCL lines, rather than byte data

@ mhectorgato. All MikroBusNet drivers are written in pure NETMF. So porting should not be an issue. However, the driver for the ADC Click is SPI based and not I2C. I do not know what MBN Driver that you selected to model your driver from, but this is probably not going to work.

[quote=“scardinale”]

scardinale - When I started VS, I selected the MBN I2C driver template. This isn’t for the ADC Click, but a custom board.

By standard .NETMF, I meant - I believe there are some helper/extension classes for I2C in MBN that wouldn’t directly be transferable. I’m probably mistaken.

Regardless, I still want to learn how to convert the datasheet into bits/bytes.

@ mhectorgato. The extension methods in the MBN Core Driver are simply wrappers around the NetMF I2C Bus and methods. You can simply use them with the methodology that you are comfortable with.

The I2C IC that you are using looks a little tricky but not impossible to master. However, it looks like an interesting IC and has loads of potential.

One thing obvious is that you have the I2C Clock speed wrong. Try with 400. From the datasheet.

High-Speed I2C-Compatible Serial Interface
400kHz Fast Mode
1.7MHz High-Speed Mode

1 Like

You don’t need to left-shift your address. Are you using hardware or software I2C implementation?

@ scardinale - just now caught that (the speed) - thanks! I wanted to use this IC in another project, but wanted to simplify the board to learn it first. Then I noticed the ADC Click only has 4 channels.

To simplify things, I started a new Gadgeteer CerbBee project -


        GTI.I2CBus i2c;
        byte address = 0x035;
        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            Socket socket = Socket.GetSocket(2, true, null, null);
            byte[] val = new byte[1];
            i2c = GTI.I2CBusFactory.Create(socket, 0x035, 400, null);
           
            i2c.Write(new byte[] { (byte)(address << 1) });
            var x = i2c.Read(val);
        }

When I ran it I got 1 for x – is that the number of bytes read? val again is equal to 240.

(byte level programming isn’t my forte)

I shifted the address, because the first communication is slave address and then a 0 for write.

First, if you are not going to create Gadgeteer module, why bother with Gadgeteer driver? I would go with pure NETMF implementation. If you are using hardware I2C: https://www.ghielectronics.com/docs/12/i2c

Second, there is a note on that page: “netmf i2cdevice configuration requires a 7-bit address! It set the 8th R/W bit automatically.”

2 Likes

Sprigo suggested looking at the driver for the Accelerometer, and that seemed reasonable, so I was going that route.

Thanks for the reference; as mentioned in the outset, I am looking to learn this.

That document helps a quite bit, thanks.

However, looking at the sample code, how does one set the SCA/SCL pins?

Based on that code, I’ve come up with this (still in the Gadgeteet app):


        byte address = 0x035;
        // This method is run when the mainboard is powered up or reset.   
        void ProgramStarted()
        {
            I2CDevice.Configuration con = new I2CDevice.Configuration(address, 400);
            I2CDevice MyI2C = new I2CDevice(con);

            I2CDevice.I2CTransaction[] xActions = new I2CDevice.I2CTransaction[2];
            
            // create write buffer (we need one byte)
            byte[] RegisterNum = new byte[1] { 105 };

            xActions[0] = I2CDevice.CreateWriteTransaction(RegisterNum);
            // create read buffer to read the register
            byte[] RegisterValue = new byte[1];
            xActions[1] = I2CDevice.CreateReadTransaction(RegisterValue);

            if (MyI2C.Execute(xActions, 1000) == 0)
            {
                Debug.Print("Failed to perform I2C transaction");
            }
            else
            {
                Debug.Print("Register value: " + RegisterValue[0].ToString());
            }
        }

So based on the top table (in the attached image) - I want to read ADC4

0 - REG
1 - SCAN1
1 - SCAN2
0 - CS3
1 - CS2
0 - CS1
0 - CS0
1 - DIF

Does this have some semblance of being correct?

Hardware I2C has dedicated pins. It assumes your device uses these predefined pins. With Software I2C you choose which digital pins to use.

1 Like

Gotcha.

So if I use an mCu that has multiple I2C buses (my Spider has 4 I ports), would I be able to select which one based on the code in Gadgeteer core - factor create code below?


 Cpu.Pin reservedSclPin = socket.ReservePin(sclPin, module);
Cpu.Pin reservedSdaPin = socket.ReservePin(sdaPin, module);

Cpu.Pin nativeSclPin, nativeSdaPin;
HardwareProvider.HwProvider.GetI2CPins(out nativeSclPin, out nativeSdaPin);

 // native implementation is preferred to an indirected one
if (reservedSdaPin == nativeSdaPin && reservedSclPin == nativeSclPin)
                return new NativeI2CBus(socket, address, clockRateKhz, module);

else if (socket.I2CBusIndirector != null)
                return socket.I2CBusIndirector(socket, sdaPin, sclPin, address, clockRateKhz, module);

else
                return new SoftwareI2CBus(socket, sdaPin, sclPin, address, clockRateKhz, module);

edit — after re-reading it; it looks like it’s expecting only 1 set of pins to be the i2c pins. So then why are there 4 I sockets on my Spider? Are the others SoftwareI2C?

[quote=“mhectorgato”]So then why are there 4 I sockets on my Spider? Are the others SoftwareI2C?
[/quote]
You can connect many I2C slave devices on one I2C bus, that’s why you use specific address when you are communicating with your device.

2 Likes

@ mhectorgato - If you look at the Spider’s schematics
http://www.ghielectronics.com/downloads/schematic/FEZ_Spider_Mainboard_SCH.PDF

You will see that all 4 “I” sockets have the same I2C pins IO11 and IO12, so you can have 4 devices on the same bus as longvas addresses of these devices are different as @ iamin has pointed out.

1 Like

You do not need to send the address in the actions array. NETMF is smart enough to do it when you “Execute” the transaction, provided that you have sent the configuration before or, as you did, you used our extension methods that send it in the Execute() method.

So I would code like this :


 var result = new Byte[1];
            var actions = new I2CDevice.I2CTransaction[1];
            actions[0] = I2CDevice.CreateReadTransaction(result);

            // Don't forget to use Hardware.I2CBus with the extension method, so that the configuration is correct
            Hardware.I2CBus.Execute(_config, actions, 1000);
            var retval = result[0];

When you see such things in the datasheet (sending the address first), it is because other language, like C, need to do this, as they operate at a “lower” level.

You will also see the same thing with SPI where the datasheet will tell you to lower the CS line, then read/write on the SPI bus and then raise the CS line again. In NETMF, you do not have to bother with this because the SPI.Write() does this for you.

About I²C address shifting : as long as the address given in the datasheet is less than 127/0x7F, it is a 7 bits address and thus does not need any shifting and can be used as is.
If it is greater than 0x7F, then a right shift is needed because it is a 8 bits address.

Depending on the manufacturer, addresses are given in both forms, which is sometimes confusing.

2 Likes

Thanks for explanations everyone.

I didn’t know that the addressing and read/write transaction bit was handled by the framework.

This has really helped and I feel that I have something to go on now!

2 questions:

  1. Could I have 2 of these same ICs on the same I2C bus, as they would have the same slave address?

  2. What if I come across an IC that has an 8-bit address? Will .NETMF take care of that as well?