TinyClr I2C and Cerberus bug?

I try to connect a gyro (or TempHumid SI70) on Cerberus on Socket 1 (or 2 same result). I can’t talk with it with TinyClr 1.0.0.preview1.
If I try same app with FEZ Spider all is working.

Maybe a bug (only Cerberus or STM32 …) ?

App code:

using System;
using System.Diagnostics;
using System.Threading;
using Bauland.Gadgeteer;
using GHIElectronics.TinyCLR.Pins;
// ReSharper disable FunctionNeverReturns

namespace TestGyro
{
    static class Program
    {
        private static Gyro _gyro;
        static void Main()
        {
            // Gyro connected on Socket 2 (Type I) of FEZ Cerberus mainboard.
            // _gyro = new Gyro(FEZCerberus.I2cBus.Socket2);
            _gyro=new Gyro(FEZSpider.I2cBus.Socket3);

            Debug.WriteLine("Don't move sensor");
            _gyro.Calibrate();
            Thread.Sleep(1000);
            Debug.WriteLine("You can move sensor");
            _gyro.MeasurementComplete += Gyro_MeasurementComplete;
            _gyro.MeasurementInterval = TimeSpan.FromSeconds(1);
            _gyro.StartTakingMeasurements();

            while (true)
            {
                Thread.Sleep(20);
            }
        }

        private static void Gyro_MeasurementComplete(Gyro sender, Gyro.MeasurementCompleteEventArgs e)
        {
            Debug.WriteLine("Results: Temp.: " + e.Temperature.ToString("F1") + "°C, X: " + e.X.ToString("F1") + ",Y: " + e.Y.ToString("F1") + ",Z: " + e.Z.ToString("F1"));
        }
    }
}

Module code:

using System;
using System.Diagnostics;
using GHIElectronics.TinyCLR.Devices.I2c;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable EventNeverSubscribedTo.Global

namespace Bauland.Gadgeteer
{
    /// <summary>
    /// Wrapper class for Gyro Gadgeteer Module
    /// </summary>
    public class Gyro
    {
        private readonly Timer _timer;
        private readonly I2cDevice _i2CDevice;
        private readonly byte[] _readBuffer1;
        private readonly byte[] _writeBuffer1;
        private readonly byte[] _writeBuffer2;
        private readonly byte[] _readBuffer8;
        private double _offsetX;
        private double _offsetY;
        private double _offsetZ;

        private MeasurementCompleteEventHandler _onMeasurementComplete;

        /// <summary>Represents the delegate used for the MeasurementComplete event.</summary>
        /// <param name="sender">The object that raised the event.</param>
        /// <param name="e">The event arguments.</param>
        public delegate void MeasurementCompleteEventHandler(Gyro sender, MeasurementCompleteEventArgs e);

        /// <summary>Raised when a measurement reading is complete.</summary>
        public event MeasurementCompleteEventHandler MeasurementComplete;

        /// <summary>
        /// The low pass filter configuration. Note that setting the low pass filter to 256Hz results in a maximum internal sample rate of 8kHz. Any other setting results in a maximum sample rate of
        /// 1kHz. The sample rate can be further divided by using SampleRateDivider.
        /// </summary>
        public Bandwidth LowPassFilter
        {
            get => (Bandwidth)(Read(Register.Scale) & 0x7);
            set => Write(Register.Scale, (byte)((byte)value | 0x18));
        }

        /// <summary>
        /// the internal sample rate divider. The gyro outputs are sampled internally at either 8kHz (if the LowPassFilter is set to 256Hz) or 1kHz for any other LowPassFilter settings. This setting
        /// can be used to further divide the sample rate.
        /// </summary>
        public byte SampleRateDivider
        {
            get => Read(Register.SampleRateDivider);
            set => Write(Register.SampleRateDivider, value);
        }

        /// <summary>The interval at which measurements are taken.</summary>
        public TimeSpan MeasurementInterval
        {
            get => _timer.Interval;
            set
            {
                var wasRunning = _timer.IsRunning;

                _timer.Stop();
                _timer.Interval = value;

                if (wasRunning)
                    _timer.Start();
            }
        }

        /// <summary>Whether or not the driver is currently taking measurements.</summary>
        public bool IsTakingMeasurements => _timer.IsRunning;

        /// <summary>Available low pass filter bandwidth settings.</summary>
        public enum Bandwidth
        {
            /// <summary>256Hz</summary>
            Hertz256 = 0,
            /// <summary>188Hz</summary>
            Hertz188 = 1,
            /// <summary>98Hz</summary>
            Hertz98 = 2,
            /// <summary>42Hz</summary>
            Hertz42 = 3,
            /// <summary>20Hz</summary>
            Hertz20 = 4,
            /// <summary>10Hz</summary>
            Hertz10 = 5,
            /// <summary>5Hz</summary>
            Hertz5 = 6
        }

        private enum Register : byte
        {
            SampleRateDivider = 0x15,
            Scale = 0x16,
            TemperatureOutHigh = 0x1B,
        }

        /// <summary>
        /// Constructor of Gyro
        /// </summary>
        /// <param name="deviceId">string of i2c bus of I Socket</param>
        public Gyro(string deviceId)
        {
            //Socket socket = Socket.GetSocket(socketNumber, true, this, null);
            //socket.EnsureTypeIsSupported('I', this);

            _readBuffer1 = new byte[1];
            _writeBuffer1 = new byte[1];
            _writeBuffer2 = new byte[2];
            _readBuffer8 = new byte[8];

            _offsetX = 0;
            _offsetY = 0;
            _offsetZ = 0;


            // _i2CDevice = GTI.I2CBusFactory.Create(socket, 0x68, 100, this);
            _i2CDevice = I2cController.FromName(deviceId).GetDevice(new I2cConnectionSettings(0x68) { BusSpeed = I2cBusSpeed.FastMode });

            SetFullScaleRange();
            _timer = new Timer(TimeSpan.FromMilliseconds(200));
            _timer.Tick += TakeMeasurement;
        }

        /// <summary>Calibrates the gyro values. Ensure that the sensor is not moving when calling this method.</summary>
        public void Calibrate()
        {
            Read(Register.TemperatureOutHigh, _readBuffer8);
            int rawX = (_readBuffer8[2] << 8) | _readBuffer8[3];
            int rawY = (_readBuffer8[4] << 8) | _readBuffer8[5];
            int rawZ = (_readBuffer8[6] << 8) | _readBuffer8[7];

            rawX = (rawX >> 15 == 1 ? -32767 : 0) + (rawX & 0x7FFF);
            rawY = (rawY >> 15 == 1 ? -32767 : 0) + (rawY & 0x7FFF);
            rawZ = (rawZ >> 15 == 1 ? -32767 : 0) + (rawZ & 0x7FFF);

            _offsetX = rawX / -14.375;
            _offsetY = rawY / -14.375;
            _offsetZ = rawZ / -14.375;
        }

        /// <summary>Obtains a single measurement and raises the event when complete.</summary>
        public void RequestSingleMeasurement()
        {
            if (IsTakingMeasurements) throw new InvalidOperationException("You cannot request a single measurement while continuous measurements are being taken.");

            _timer.Behavior = Timer.BehaviorType.RunOnce;
            _timer.Start();
        }

        /// <summary>Starts taking measurements and fires MeasurementComplete when a new measurement is available.</summary>
        public void StartTakingMeasurements()
        {
            _timer.Behavior = Timer.BehaviorType.RunContinuously;
            _timer.Start();
        }

        /// <summary>Stops taking measurements.</summary>
        public void StopTakingMeasurements()
        {
            _timer.Stop();
        }

        private void SetFullScaleRange()
        {
            Write(Register.Scale, (byte)(Read(Register.Scale) | 0x18));
        }

        private byte Read(Register register)
        {
            _writeBuffer1[0] = (byte)register;
            _i2CDevice.WriteRead(_writeBuffer1, _readBuffer1);
            return _readBuffer1[0];
        }

        private void Read(Register register, byte[] readBuffer)
        {
            _writeBuffer1[0] = (byte)register;
            _i2CDevice.WriteRead(_writeBuffer1, readBuffer);
        }

        private void Write(Register register, byte value)
        {
            _writeBuffer2[0] = (byte)register;
            _writeBuffer2[1] = value;
            _i2CDevice.Write(_writeBuffer2);
        }

        private void TakeMeasurement(object sender, EventHandlerArgs args)
        {
            Read(Register.TemperatureOutHigh, _readBuffer8);

            int rawT = (_readBuffer8[0] << 8) | _readBuffer8[1];
            int rawX = (_readBuffer8[2] << 8) | _readBuffer8[3];
            int rawY = (_readBuffer8[4] << 8) | _readBuffer8[5];
            int rawZ = (_readBuffer8[6] << 8) | _readBuffer8[7];

            rawT = (rawT >> 15 == 1 ? -32767 : 0) + (rawT & 0x7FFF);
            rawX = (rawX >> 15 == 1 ? -32767 : 0) + (rawX & 0x7FFF);
            rawY = (rawY >> 15 == 1 ? -32767 : 0) + (rawY & 0x7FFF);
            rawZ = (rawZ >> 15 == 1 ? -32767 : 0) + (rawZ & 0x7FFF);

            double x = rawX / 14.375 + _offsetX;
            double y = rawY / 14.375 + _offsetY;
            double z = rawZ / 14.375 + _offsetZ;
            double t = (rawT + 13200) / 280.0 + 35;

            OnMeasurementComplete(this, new MeasurementCompleteEventArgs(x, y, z, t));
        }

        private void OnMeasurementComplete(Gyro sender, MeasurementCompleteEventArgs e)
        {
            if (_onMeasurementComplete == null)
                _onMeasurementComplete = OnMeasurementComplete;

            MeasurementComplete?.Invoke(sender, e);
        }

        /// <summary>Event arguments for the MeasurementComplete event.</summary>
        public class MeasurementCompleteEventArgs : EventArgs
        {
            /// <summary>Angular rate around the X axis (roll) in degree per second.</summary>
            public double X { get; }

            /// <summary>Angular rate around the Y axis (pitch) in degree per second.</summary>
            public double Y { get; }

            /// <summary>Angular rate around the Z axis (yaw) in degree per second.</summary>
            public double Z { get; }

            /// <summary>Temperature in degree celsius.</summary>
            public double Temperature { get; }

            internal MeasurementCompleteEventArgs(double x, double y, double z, double temperature)
            {
                X = x;
                Y = y;
                Z = z;
                Temperature = temperature;
            }

            /// <summary>Provides a string representation of the instance.</summary>
            /// <returns>A string describing the values contained in the object.</returns>
            public override string ToString()
            {
                return "X: " + X.ToString("f2") + " Y: " + Y.ToString("f2") + " Z: " + Z.ToString("f2") + " Temperature: " + Temperature.ToString("f2");
            }
        }
    }
}

If the same code (changing only the ID) works on the Spider, but not the Cerberus, we’ll take a look for the next release.

Do you have a sample with the TempHumidSI70? That’ll be a bit easier to test.

As I use SoftwareI2c for TempHumidSI70, it could be related to error of SoftwareI2c of 1.0.0.preview1 and not I2C really. Else code is here:

Thanks, we’ll take a look. You can track the progress at Investigate I2C failures on Cerb · Issue #416 · ghi-electronics/TinyCLR-Ports · GitHub

Do you have any GHI accelerometer module to give it a try?

I have one.

Can you please try I2C with GHI accelerometer module?

@Dat_Tran: I have tried with accel. And I’ve got no issue with it.
I try again with gyro: first transaction fails but next are correct with Cerberus althrough all transactions are correct with Spider.
To test I just use the WHO_AM_I register:

        private static void Loop()
        {
            var buff = new byte[1];

            // Working for Accel
            //var ret=_i2c.WriteReadPartial(new byte[] { 0x0d }, buff);

            // Working always for Gyro with Spider, first transaction have status 2 with Cerberus
            var ret=_i2c.WriteReadPartial(new byte[] { 0x00 }, buff);

            Debug.WriteLine(buff[0].ToString("X") + " - " + ret.Status);
            Thread.Sleep(1000);
        }

Result for EMX is:

EMX
68 - 0
68 - 0
68 - 0
68 - 0
68 - 0

And for Cerberus:

Cerb
00 - 2
68 - 0
68 - 0
68 - 0
68 - 0

I had similar issues when used Pololu Zumobot with FEZ. I thought I was doing something wrong.

Good, I am not crazy :smile:

I enjoy I’m not alone. I was thinking I was crazy too :wink:

I too have the same problem with a Thermo3 click (TMP102). No response when reading device ID register or anything else.

I cannot say I can’t read register. I can’t read the first time and only with Cerberus. With Spider board, first time reading is working.

I am using a Quail. I get no response from the device using TinyCLR. Verified the device to be working using NetMF