grovepi/Software/CSharp/GrovePi/I2CDevices/SixAxisAccelerometerAndCompass.cs
2025-03-21 16:04:17 +01:00

405 lines
No EOL
11 KiB
C#

using System;
using System.Linq;
using GrovePi.I2CDevices.Configuration;
using Windows.Devices.I2c;
using static System.Math;
namespace GrovePi.I2CDevices
{
public interface ISixAxisAccelerometerAndCompass
{
byte DeviceId();
double[] GetAcceleration();
double[] GetMagnetic();
double GetHeading();
double GetTiltHeading();
ISixAxisAccelerometerAndCompass Configure(Action<ISixAxisAccelerometerAndCompassConfiguration> configuration);
}
public interface ISixAxisAccelerometerAndCompassConfiguration
{
void AccelerationAxes(AccelerationAxes value);
void AccelerationDataRate(AccelerationDataRate value);
void AccelerationDataUpdateMode(AccelerationDataUpdateMode value);
void AccelerationScale(AccelerationScale value);
void MagneticDataRate(MagneticDataRate value);
void MagneticMode(MagneticMode value);
void MagneticResolution(MagneticResolution value);
void MagneticScale(MagneticScale value);
}
internal sealed class SixAxisAccelerometerAndCompass : ISixAxisAccelerometerAndCompass
{
private const int X = 0;
private const int Y = 1;
private const int Z = 2;
private const double Pow_2_15 = 32768;
private const byte WHO_AM_I = 0x0f;
private const byte CTRL_REG0 = 0x1F;
private const byte CTRL_REG1 = 0x20;
private const byte CTRL_REG2 = 0x21;
private const byte CTRL_REG3 = 0x22;
private const byte CTRL_REG4 = 0x23;
private const byte CTRL_REG5 = 0x24;
private const byte CTRL_REG6 = 0x25;
private const byte CTRL_REG7 = 0x26;
private const byte OUT_X_L_M = 0x08;
private const byte OUT_X_H_M = 0x09;
private const byte OUT_Y_L_M = 0x0A;
private const byte OUT_Y_H_M = 0x0B;
private const byte OUT_Z_L_M = 0x0C;
private const byte OUT_Z_H_M = 0x0D;
private const byte OUT_X_L_A = 0x28;
private const byte OUT_X_H_A = 0x29;
private const byte OUT_Y_L_A = 0x2A;
private const byte OUT_Y_H_A = 0x2B;
private const byte OUT_Z_L_A = 0x2C;
private const byte OUT_Z_H_A = 0x2D;
internal I2cDevice DirectAccess
{
get;
}
internal AccelerationAxes AccelerationAxes
{
get;
set;
} = AccelerationAxes.XYZ;
internal AccelerationDataRate AccelerationDataRate
{
get;
set;
} = AccelerationDataRate.Hz_50;
internal AccelerationDataUpdateMode AccelerationDataUpdateMode
{
get;
set;
} = AccelerationDataUpdateMode.Continuous;
internal AccelerationScale AccelerationScale
{
get
{
return accelerationScale;
}
set
{
accelerationScale = value;
switch (accelerationScale)
{
case AccelerationScale.G_2:
accelerationScaleFactor = 2.0;
break;
case AccelerationScale.G_4:
accelerationScaleFactor = 4.0;
break;
case AccelerationScale.G_6:
accelerationScaleFactor = 6.0;
break;
case AccelerationScale.G_8:
accelerationScaleFactor = 8.0;
break;
case AccelerationScale.G_16:
accelerationScaleFactor = 16.0;
break;
}
}
}
private AccelerationScale accelerationScale = AccelerationScale.G_2;
private double accelerationScaleFactor = 2.0;
internal MagneticDataRate MagneticDataRate
{
get;
set;
} = MagneticDataRate.Hz_50;
internal MagneticMode MagneticMode
{
get;
set;
} = MagneticMode.ContinousConversion;
internal MagneticResolution MagneticResolution
{
get;
set;
} = MagneticResolution.Low;
internal MagneticScale MagneticScale
{
get;
set;
} = MagneticScale.Gs_4;
internal SixAxisAccelerometerAndCompass(I2cDevice device)
{
if (device == null)
throw new ArgumentNullException(nameof(device));
DirectAccess = device;
Reconfigure();
}
public byte DeviceId() => ReadRegister(WHO_AM_I);
public double[] GetAcceleration()
{
// Windows is little-endian so read registers from low to high
var input = new[]
{
ReadRegister(OUT_X_L_A), ReadRegister(OUT_X_H_A),
ReadRegister(OUT_Y_L_A), ReadRegister(OUT_Y_H_A),
ReadRegister(OUT_Z_L_A), ReadRegister(OUT_Z_H_A)
};
// calculate two's complement value and scale by acceleration factor
var output = new[]
{
BitConverter.ToInt16(input, 0) / Pow_2_15 * accelerationScaleFactor,
BitConverter.ToInt16(input, 2) / Pow_2_15 * accelerationScaleFactor,
BitConverter.ToInt16(input, 4) / Pow_2_15 * accelerationScaleFactor
};
return output;
}
public double[] GetMagnetic()
{
// Windows is little-endian so read registers from low to high
var input = new[]
{
ReadRegister(OUT_X_L_M), ReadRegister(OUT_X_H_M),
ReadRegister(OUT_Y_L_M), ReadRegister(OUT_Y_H_M),
ReadRegister(OUT_Z_L_M), ReadRegister(OUT_Z_H_M)
};
// calculate two's complement value
var output = new[]
{
BitConverter.ToInt16(input, 0) / Pow_2_15,
BitConverter.ToInt16(input, 2) / Pow_2_15,
BitConverter.ToInt16(input, 4) / Pow_2_15
};
return output;
}
public double GetHeading()
{
var magnetic = GetMagnetic();
var heading = 180 * Atan2(magnetic[Y], magnetic[X]) / PI;
return (heading < 0) ? (heading + 360) : heading;
}
public double GetTiltHeading()
{
var acceleration = GetAcceleration();
var pitch = Asin(-acceleration[X]);
var roll = Asin(acceleration[Y] / Cos(pitch));
var magnetic = GetMagnetic();
var xh = magnetic[X] * Cos(pitch) + magnetic[Z] * Sin(pitch);
var yh = magnetic[X] * Sin(roll) * Sin(pitch) + magnetic[Y] * Cos(roll) - magnetic[Z] * Sin(roll) * Cos(pitch);
var heading = 180 * Atan2(yh, xh) / PI;
return (heading < 0) ? (heading + 360) : heading;
}
public ISixAxisAccelerometerAndCompass Configure(Action<ISixAxisAccelerometerAndCompassConfiguration> configuration)
{
if (configuration != null)
{
configuration(new SixAxisAccelerometerAndCompassConfiguration(this));
Reconfigure();
}
return this;
}
private byte ReadRegister(byte register)
{
var buffer = new byte[1];
DirectAccess.WriteRead(new[] { register }, buffer);
return buffer[0];
}
private void Reconfigure()
{
DirectAccess.Write(new[] { CTRL_REG0, (byte)0 }); // normal mode, FIFO disabled, high-pass filter disabled
DirectAccess.Write(new[] { CTRL_REG1, (byte)((byte)AccelerationDataRate | (byte)AccelerationDataUpdateMode | (byte)AccelerationAxes) });
DirectAccess.Write(new[] { CTRL_REG2, (byte)AccelerationScale });
DirectAccess.Write(new[] { CTRL_REG3, (byte)0 }); // interrupt 1 disabled
DirectAccess.Write(new[] { CTRL_REG4, (byte)0 }); // interrupt 2 disabled
DirectAccess.Write(new[] { CTRL_REG5, (byte)((byte)MagneticResolution | (byte)MagneticDataRate) });
DirectAccess.Write(new[] { CTRL_REG6, (byte)MagneticScale });
DirectAccess.Write(new[] { CTRL_REG7, (byte)MagneticMode });
}
}
internal sealed class SixAxisAccelerometerAndCompassConfiguration : ISixAxisAccelerometerAndCompassConfiguration
{
private readonly SixAxisAccelerometerAndCompass _sensor;
public SixAxisAccelerometerAndCompassConfiguration(SixAxisAccelerometerAndCompass sensor)
{
_sensor = sensor;
}
public void AccelerationAxes(AccelerationAxes value)
{
_sensor.AccelerationAxes = value;
}
public void AccelerationDataRate(AccelerationDataRate value)
{
_sensor.AccelerationDataRate = value;
}
public void AccelerationDataUpdateMode(AccelerationDataUpdateMode value)
{
_sensor.AccelerationDataUpdateMode = value;
}
public void AccelerationScale(AccelerationScale value)
{
_sensor.AccelerationScale = value;
}
public void MagneticDataRate(MagneticDataRate value)
{
if ((value == Configuration.MagneticDataRate.Hz_100) && (_sensor.AccelerationDataRate > Configuration.AccelerationDataRate.None) && (_sensor.AccelerationDataRate <= Configuration.AccelerationDataRate.Hz_50))
{
_sensor.AccelerationDataRate = Configuration.AccelerationDataRate.Hz_100;
}
_sensor.MagneticDataRate = value;
}
public void MagneticMode(MagneticMode value)
{
_sensor.MagneticMode = value;
}
public void MagneticResolution(MagneticResolution value)
{
_sensor.MagneticResolution = value;
}
public void MagneticScale(MagneticScale value)
{
_sensor.MagneticScale = value;
}
}
namespace Configuration
{
[Flags]
public enum AccelerationAxes : byte
{
None = 0,
X = 4,
Y = 2,
Z = 1,
XYZ = X | Y | Z
}
public enum AccelerationDataRate : byte
{
None = 0,
Hz_3_125 = 16,
Hz_6_25 = 32,
Hz_12_5 = 48,
Hz_25 = 64,
Hz_50 = 80,
Hz_100 = 96,
Hz_200 = 112,
Hz_400 = 128,
Hz_800 = 144,
Hz_1600 = 160
}
public enum AccelerationDataUpdateMode : byte
{
Continuous = 0,
OnRead = 8
}
public enum AccelerationScale : byte
{
G_2 = 0,
G_4 = 8,
G_6 = 16,
G_8 = 24,
G_16 = 32
}
public enum MagneticMode : byte
{
ContinousConversion = 0,
SingleConversion = 1,
PowerDown = 3
}
public enum MagneticDataRate : byte
{
Hz_3_125 = 0,
Hz_6_25 = 4,
Hz_12_5 = 8,
Hz_25 = 12,
Hz_50 = 16,
Hz_100 = 20
}
public enum MagneticResolution : byte
{
Low = 0,
High = 96
}
public enum MagneticScale : byte
{
Gs_2 = 0,
Gs_4 = 32,
Gs_8 = 64,
Gs_12 = 96
}
}
}