diff --git a/OpenEphys.Onix1/ConfigurePolledBno055.cs b/OpenEphys.Onix1/ConfigurePolledBno055.cs index 5577f82..3bbda70 100644 --- a/OpenEphys.Onix1/ConfigurePolledBno055.cs +++ b/OpenEphys.Onix1/ConfigurePolledBno055.cs @@ -5,8 +5,7 @@ namespace OpenEphys.Onix1 { /// - /// Configures a Bosch Bno055 9-axis inertial measurement unit (IMU) that is polled by the - /// computer. + /// Configures a Bosch Bno055 9-axis inertial measurement unit (IMU) that is polled by the host computer. /// /// /// This configuration operator can be linked to a data IO operator, such as - /// Copy constructor for the class. + /// Initializes a new instance of with its properties copied from an + /// existing instance. /// /// A pre-existing object. public ConfigurePolledBno055(ConfigurePolledBno055 configurePolledBno055) @@ -126,7 +126,7 @@ void ConfigureBno055(DeviceContext device) static class PolledBno055 { public const int BNO055Address = 0x28; - public const int DataAddress = 0x1A; + public const int EulerHeadingLsbAddress = 0x1A; internal class NameConverter : DeviceNameConverter { @@ -216,7 +216,7 @@ public enum Bno055AxisSign : uint MirrorX = 0b00000_100, } - // NB: Can be used to remove axis map and sign properties from MutliDeviceFactories that include a + // NB: Can be used to remove axis map and sign properties from MultiDeviceFactories that include a // ConfigurePolledBno055 when having those options would cause confusion and potential // commutator malfunction internal class PolledBno055SingleDeviceFactoryConverter : SingleDeviceFactoryConverter diff --git a/OpenEphys.Onix1/PolledBno055Data.cs b/OpenEphys.Onix1/PolledBno055Data.cs index 884d66c..876ed88 100644 --- a/OpenEphys.Onix1/PolledBno055Data.cs +++ b/OpenEphys.Onix1/PolledBno055Data.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.Linq; +using System.Reactive.Concurrency; using System.Reactive.Linq; using Bonsai; @@ -8,7 +9,7 @@ namespace OpenEphys.Onix1 { /// /// Produces a sequence of Bno055DataFrames from Bosch Bno055 - /// 9-axis inertial measurement unit (IMU) + /// 9-axis inertial measurement unit (IMU) by polling it from the host computer. /// /// /// This data IO operator must be linked to an appropriate configuration, such as a public string DeviceName { get; set; } /// - /// Generates a sequence of Bno055DataFrames at approximately 100 - /// Hz. + /// Gets or sets which data registers should be collected from the Bno055. /// /// - /// This will generate a sequence of Bno055DataFrames at approximately 100 Hz. - /// This rate may be limited by the hardware. + /// The rate that data is sampled is limited by communication with the Bno055 by the host PC (rather + /// than a dedicated controller on the headstage). If the user wishes to maximize the sampling rate of + /// particular measurements, they can select which should be sampled using this property. For + /// instance, specifying "Quaternion, Calibration" means that only the quaternion and sensor + /// calibration status registers will be polled and the rest of the members of , with the exception of Clock and HubClock, will be set to + /// 0. + /// + [Category(DeviceFactory.ConfigurationCategory)] + [Description("Specifies which data registers should be read from the chip.")] + [TypeConverter(typeof(FlagsConverter))] + public PolledBno055Registers PolledRegisters { get; set; } = PolledBno055Registers.All; + + /// + /// Use a ~100 Hz internal timer to generate a sequence of Bno055DataFrames. + /// + /// + /// An internal timer will be used to poll the Bno055 in order to generate a sequence of Bno055DataFrames at approximately 100 Hz. This rate may be limited by + /// hardware communication speeds (see ). /// /// A sequence of Bno055DataFrames. public override IObservable Generate() { - // Max of 100 Hz, but limited by I2C bus - var source = Observable.Interval(TimeSpan.FromSeconds(0.01)); + // NB: NewThreadScheduler runs polling on dedicated thread and uses StopWatch for high resolution + // periodic measurements. This results in much better performance than the default scheduler. + var source = Observable.Interval(TimeSpan.FromMilliseconds(10), new NewThreadScheduler()); return Generate(source); } @@ -44,14 +63,17 @@ public override IObservable Generate() /// input sequence. /// /// - /// This will attempt to produce a sequence of Bno055DataFrames that is updated whenever - /// an item in the sequence is received. This rate is be limited by the - /// hardware and has a maximum meaningful rate of 100 Hz. + /// This will attempt to produce a sequence of Bno055DataFrames that + /// is updated whenever an item in the sequence is received. This rate may + /// be limited by the hardware communication speeds (see ) and has a + /// maximum meaningful rate of 100 Hz, which is the update rate of the sensor fusion algorithm on the + /// Bno055 itself. /// /// A sequence to drive sampling. - /// A sequence of objects. + /// A sequence of Bno055DataFrames. public unsafe IObservable Generate(IObservable source) { + var polled = PolledRegisters; return DeviceManager.GetDevice(DeviceName).SelectMany( deviceInfo => { @@ -68,7 +90,42 @@ public unsafe IObservable Generate(IObservable { - var data = i2c.ReadBytes(PolledBno055.DataAddress, sizeof(Bno055DataPayload)); + byte[] data = { + polled.HasFlag(PolledBno055Registers.EulerAngle) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 0) : (byte)0, + polled.HasFlag(PolledBno055Registers.EulerAngle) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 1) : (byte)0, + polled.HasFlag(PolledBno055Registers.EulerAngle) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 2) : (byte)0, + polled.HasFlag(PolledBno055Registers.EulerAngle) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 3) : (byte)0, + polled.HasFlag(PolledBno055Registers.EulerAngle) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 4) : (byte)0, + polled.HasFlag(PolledBno055Registers.EulerAngle) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 5) : (byte)0, + + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 6) : (byte)0, + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 7) : (byte)0, + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 8) : (byte)0, + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 9) : (byte)0, + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 10) : (byte)0, + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 11) : (byte)0, + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 12) : (byte)0, + polled.HasFlag(PolledBno055Registers.Quaternion) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 13) : (byte)0, + + polled.HasFlag(PolledBno055Registers.Acceleration) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 14) : (byte)0, + polled.HasFlag(PolledBno055Registers.Acceleration) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 15) : (byte)0, + polled.HasFlag(PolledBno055Registers.Acceleration) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 16) : (byte)0, + polled.HasFlag(PolledBno055Registers.Acceleration) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 17) : (byte)0, + polled.HasFlag(PolledBno055Registers.Acceleration) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 18) : (byte)0, + polled.HasFlag(PolledBno055Registers.Acceleration) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 19) : (byte)0, + + polled.HasFlag(PolledBno055Registers.Gravity) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 20) : (byte)0, + polled.HasFlag(PolledBno055Registers.Gravity) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 21) : (byte)0, + polled.HasFlag(PolledBno055Registers.Gravity) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 22) : (byte)0, + polled.HasFlag(PolledBno055Registers.Gravity) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 23) : (byte)0, + polled.HasFlag(PolledBno055Registers.Gravity) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 24) : (byte)0, + polled.HasFlag(PolledBno055Registers.Gravity) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 25) : (byte)0, + + polled.HasFlag(PolledBno055Registers.Temperature) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 26) : (byte)0, + + polled.HasFlag(PolledBno055Registers.Calibration) ? i2c.ReadByte(PolledBno055.EulerHeadingLsbAddress + 27) : (byte)0, + }; + ulong clock = passthrough.ReadRegister(DS90UB9x.LASTI2CL); clock += (ulong)passthrough.ReadRegister(DS90UB9x.LASTI2CH) << 32; fixed (byte* dataPtr = data) @@ -85,6 +142,69 @@ public unsafe IObservable Generate(IObservable + /// Specifies which data registers will be read from the Bno055 during each polling cycle. + /// + [Flags] + public enum PolledBno055Registers + { + /// + /// Specifies that the Euler angles will be polled. + /// + EulerAngle = 0x1, + + /// + /// Specifies that the quaternion will be polled. + /// + Quaternion = 0x2, + + /// + /// Specifies that the linear acceleration will be polled. + /// + Acceleration = 0x4, + + /// + /// Specifies that the gravity vector will be polled. + /// + Gravity = 0x8, + + /// + /// Specifies that the temperature measurement will be polled. + /// + Temperature = 0x10, + + /// + /// Specifies that the sensor calibration status will be polled. + /// + Calibration = 0x20, + + /// + /// Specifies that all sensor measurements and calibration status will be polled. + /// + All = EulerAngle | Quaternion | Acceleration | Gravity | Temperature | Calibration, } /// v @@ -98,5 +218,4 @@ public class NeuropixelsV2eBno055Data : PolledBno055Data { } /// v [Obsolete("This operator is obsolete. Use PolledBno055Data instead. Will be removed in version 1.0.0.")] public class NeuropixelsV2eBetaBno055Data : PolledBno055Data { } - }