Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for UCLA Miniscope V4 #115

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public ConfigureFmcLinkController()
"Consult the device datasheet and documentation for allowable voltage ranges.")]
public double? PortVoltage { get; set; } = null;

protected bool CheckLinkState(DeviceContext device)
protected virtual bool CheckLinkState(DeviceContext device)
{
var linkState = device.ReadRegister(FmcLinkController.LINKSTATE);
return (linkState & FmcLinkController.LINKSTATE_SL) != 0;
Expand Down
54 changes: 54 additions & 0 deletions OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.ComponentModel;

namespace OpenEphys.Onix
{
public class ConfigureUclaMiniscopeV4 : HubDeviceFactory
{
PortName port;
readonly ConfigureUclaMiniscopeV4LinkController LinkController = new();

public ConfigureUclaMiniscopeV4()
{
Port = PortName.PortA;
LinkController.HubConfiguration = HubConfiguration.Passthrough;
}

[Category(ConfigurationCategory)]
[TypeConverter(typeof(HubDeviceConverter))]
public ConfigureUclaMiniscopeV4Camera Camera { get; set; } = new();

[Category(ConfigurationCategory)]
[TypeConverter(typeof(HubDeviceConverter))]
public ConfigureUclaMiniscopeV4Bno055 Bno055 { get; set; } = new();

public PortName Port
{
get { return port; }
set
{
port = value;
var offset = (uint)port << 8;
LinkController.DeviceAddress = (uint)port;
Camera.DeviceAddress = offset + 0;
Bno055.DeviceAddress = offset + 1;
}
}

[Description("If defined, it will override automated voltage discovery and apply the specified voltage" +
"to the headstage. Warning: this device requires 5.0V to 6.0V for proper operation." +
"Supplying higher voltages may result in damage to the headstage.")]
public double? PortVoltage
{
get => LinkController.PortVoltage;
set => LinkController.PortVoltage = value;
}

internal override IEnumerable<IDeviceConfiguration> GetDevices()
{
yield return LinkController;
yield return Camera;
yield return Bno055;
}
}
}
69 changes: 69 additions & 0 deletions OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Bno055.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.ComponentModel;

namespace OpenEphys.Onix
{
public class ConfigureUclaMiniscopeV4Bno055 : SingleDeviceFactory
{
public ConfigureUclaMiniscopeV4Bno055()
: base(typeof(UclaMiniscopeV4Bno055))
{
}

[Category(ConfigurationCategory)]
[Description("Specifies whether the BNO055 device is enabled.")]
public bool Enable { get; set; } = true;

public override IObservable<ContextTask> Process(IObservable<ContextTask> source)
{
var enable = Enable;
var deviceName = DeviceName;
var deviceAddress = DeviceAddress;
return source.ConfigureDevice(context =>
{
// configure device via the DS90UB9x deserializer device
var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID);
ConfigureDeserializer(device);
ConfigureBno055(device);
var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress);
return DeviceManager.RegisterDevice(deviceName, deviceInfo);
});
}

static void ConfigureDeserializer(DeviceContext device)
{
// configure deserializer I2C aliases
var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR);
uint alias = UclaMiniscopeV4Bno055.BNO055Address << 1;
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID4, alias);
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias4, alias);
}

static void ConfigureBno055(DeviceContext device)
{
// setup BNO055 device
// TODO: Correct orientation
var i2c = new I2CRegisterContext(device, UclaMiniscopeV4Bno055.BNO055Address);
i2c.WriteByte(0x3E, 0x00); // Power mode normal
i2c.WriteByte(0x07, 0x00); // Page ID address 0
i2c.WriteByte(0x3F, 0x00); // Internal oscillator
i2c.WriteByte(0x41, 0b00000110); // Axis map config (configured to match hs64; X => Z, Y => -Y, Z => X)
i2c.WriteByte(0x42, 0b000000010); // Axis sign (negate Y)
i2c.WriteByte(0x3D, 8); // Operation mode is NOF
}
}

static class UclaMiniscopeV4Bno055
{
public const int BNO055Address = 0x28;
public const int DataAddress = 0x1A;

internal class NameConverter : DeviceNameConverter
{
public NameConverter()
: base(typeof(UclaMiniscopeV4Bno055))
{
}
}
}
}
199 changes: 199 additions & 0 deletions OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
using System;
using System.ComponentModel;
using System.Reactive.Disposables;

namespace OpenEphys.Onix
{
public class ConfigureUclaMiniscopeV4Camera : SingleDeviceFactory
{
public ConfigureUclaMiniscopeV4Camera()
: base(typeof(UclaMiniscopeV4))
{
}

[Category(ConfigurationCategory)]
[Description("Specifies whether the camera is enabled.")]
public bool Enable { get; set; } = true;

[Category(ConfigurationCategory)]
[Description("Only turn on excitation LED during camera exposures.")]
public bool InterleaveLed { get; set; } = false;

[Category(ConfigurationCategory)]
[Description("Only turn on excitation LED during camera exposures.")]
public UclaMiniscopeV4FramesPerSecond FrameRate { get; set; } = UclaMiniscopeV4FramesPerSecond.Fps30Hz;

public override IObservable<ContextTask> Process(IObservable<ContextTask> source)
{
var enable = Enable;
var deviceName = DeviceName;
var deviceAddress = DeviceAddress;
uint shutterWidth = FrameRate switch
{
UclaMiniscopeV4FramesPerSecond.Fps10Hz => 10000,
UclaMiniscopeV4FramesPerSecond.Fps15Hz => 6667,
UclaMiniscopeV4FramesPerSecond.Fps20Hz => 5000,
UclaMiniscopeV4FramesPerSecond.Fps25Hz => 4000,
UclaMiniscopeV4FramesPerSecond.Fps30Hz => 3300,
_ => 3300
};
var interleaveLED = InterleaveLed;

return source.ConfigureDevice(context =>
{
// configure device via the DS90UB9x deserializer device
var device = context.GetPassthroughDeviceContext(deviceAddress, DS90UB9x.ID);
device.WriteRegister(DS90UB9x.ENABLE, enable ? 1u : 0);

// configure deserializer, chip states, and camera PLL
ConfigureMiniscope(device);

// configuration properties
var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress);
atMega.WriteByte(0x04, (uint)(interleaveLED ? 0x00 : 0x03));
WriteCameraRegister(atMega, 200, shutterWidth);

var deviceInfo = new DeviceInfo(context, DeviceType, deviceAddress);
var disposable = DeviceManager.RegisterDevice(deviceName, deviceInfo);
var shutdown = Disposable.Create(() =>
{
// turn off EWL
var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address);
max14574.WriteByte(0x03, 0x00);

// turn off LED
var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress);
atMega.WriteByte(1, 0xFF);
});
return new CompositeDisposable(
shutdown,
disposable);
});
}

internal static void ConfigureMiniscope(DeviceContext device)
{
// configure deserializer
device.WriteRegister(DS90UB9x.TRIGGEROFF, 0);
device.WriteRegister(DS90UB9x.READSZ, UclaMiniscopeV4.SensorColumns);
device.WriteRegister(DS90UB9x.TRIGGER, (uint)DS90UB9xTriggerMode.HsyncEdgePositive);
device.WriteRegister(DS90UB9x.SYNCBITS, 0);
device.WriteRegister(DS90UB9x.DATAGATE, (uint)DS90UB9xDataGate.VsyncPositive);

// NB: This is required because Bonsai is not garuenteed to capure every frame at the start of acqusition.
// For this reason, the frame start needs to be marked.
device.WriteRegister(DS90UB9x.MARK, (uint)DS90UB9xMarkMode.VsyncRising);

// configure deserializer I2C aliases
var deserializer = new I2CRegisterContext(device, DS90UB9x.DES_ADDR);
uint coaxMode = 0x4 + (uint)DS90UB9xMode.Raw12BitLowFrequency; // 0x4 maintains coax mode
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.PortMode, coaxMode);

uint i2cAlias = UclaMiniscopeV4.AtMegaAddress << 1;
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, i2cAlias);
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, i2cAlias);

i2cAlias = UclaMiniscopeV4.Tpl0102Address << 1;
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID2, i2cAlias);
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias2, i2cAlias);

i2cAlias = UclaMiniscopeV4.Max14574Address << 1;
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID3, i2cAlias);
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias3, i2cAlias);

// set up potentiometer
var tpl0102 = new I2CRegisterContext(device, UclaMiniscopeV4.Tpl0102Address);
tpl0102.WriteByte(0x00, 0x72);
tpl0102.WriteByte(0x01, 0x00);

// turn on EWL
var max14574 = new I2CRegisterContext(device, UclaMiniscopeV4.Max14574Address);
max14574.WriteByte(0x08, 0x7F);
max14574.WriteByte(0x09, 0x02);

// turn on LED and setup Python480
var atMega = new I2CRegisterContext(device, UclaMiniscopeV4.AtMegaAddress);
WriteCameraRegister(atMega, 16, 3); // Turn on PLL
WriteCameraRegister(atMega, 32, 0x7007); // Turn on clock managment
WriteCameraRegister(atMega, 199, 666); // Defines granularity (unit = 1/PLL clock) of exposure and reset_length
WriteCameraRegister(atMega, 200, 3300); // Set frame rate to 30 Hz
WriteCameraRegister(atMega, 201, 3000); // Set Exposure
}

private static void WriteCameraRegister(I2CRegisterContext i2c, uint register, uint value)
{
// ATMega -> Python480 passthrough protocol
var regLow = register & 0xFF;
var regHigh = (register >> 8) & 0xFF;
var valLow = value & 0xFF;
var valHigh = (value >> 8) & 0xFF;

i2c.WriteByte(0x05, regHigh);
i2c.WriteByte(regLow, valHigh);
i2c.WriteByte(valLow, 0x00);
}

internal static void SetLedBrightness(DeviceContext device, double percent)
{
var des = device.Context.GetPassthroughDeviceContext(device.Address, DS90UB9x.ID);

var atMega = new I2CRegisterContext(des, UclaMiniscopeV4.AtMegaAddress);
atMega.WriteByte(0x01, (uint)((percent == 0) ? 0xFF : 0x08));

var tpl0102 = new I2CRegisterContext(des, UclaMiniscopeV4.Tpl0102Address);
tpl0102.WriteByte(0x01, (uint)(255 * ((100 - percent) / 100.0)));
}

internal static void SetSensorGain(DeviceContext device, UclaMiniscopeV4SensorGain gain)
{
var des = device.Context.GetPassthroughDeviceContext(device.Address, DS90UB9x.ID);

var atMega = new I2CRegisterContext(des, UclaMiniscopeV4.AtMegaAddress);
WriteCameraRegister(atMega, 204, (uint)gain);
}

internal static void SetLiquidLensVoltage(DeviceContext device, double voltage)
{
var des = device.Context.GetPassthroughDeviceContext(device.Address, DS90UB9x.ID);

var max14574 = new I2CRegisterContext(des, UclaMiniscopeV4.Max14574Address);
max14574.WriteByte(0x08, (uint)((voltage - 24.4) / 0.0445) >> 2);
max14574.WriteByte(0x09, 0x02);
}

}

static class UclaMiniscopeV4
{
public const int AtMegaAddress = 0x10;
public const int Tpl0102Address = 0x50;
public const int Max14574Address = 0x77;

public const int SensorRows = 608;
public const int SensorColumns = 608;

internal class NameConverter : DeviceNameConverter
{
public NameConverter()
: base(typeof(UclaMiniscopeV4))
{
}
}
}

public enum UclaMiniscopeV4SensorGain
{
Low = 0x00E1,
Medium = 0x00E4,
High = 0x0024,
}

public enum UclaMiniscopeV4FramesPerSecond
{
Fps10Hz,
Fps15Hz,
Fps20Hz,
Fps25Hz,
Fps30Hz,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Threading;

namespace OpenEphys.Onix
{
class ConfigureUclaMiniscopeV4LinkController : ConfigureFmcLinkController
{
protected override bool ConfigurePortVoltage(DeviceContext device)
{
const uint MinVoltage = 50;
const uint MaxVoltage = 70;
const uint VoltageOffset = 02;
const uint VoltageIncrement = 02;

for (uint voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement)
{
SetPortVoltage(device, voltage);
if (CheckLinkState(device))
{
SetPortVoltage(device, voltage + VoltageOffset);
return CheckLinkState(device);
}
}

return false;
}

private void SetPortVoltage(DeviceContext device, uint voltage)
{
const int WaitUntilVoltageSettles = 200;
device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0);
Thread.Sleep(WaitUntilVoltageSettles);
device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage);
Thread.Sleep(WaitUntilVoltageSettles);
}

override protected bool CheckLinkState(DeviceContext device)
{
try
{
var ds90ub9x = device.Context.GetPassthroughDeviceContext(DeviceAddress << 8, DS90UB9x.ID);
ConfigureUclaMiniscopeV4Camera.ConfigureMiniscope(ds90ub9x);
}
catch (oni.ONIException ex)
{
// this can occur if power is too low, so we need to be able to try again
const int FailureToWriteRegister = -6;
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
if (ex.Number != FailureToWriteRegister)
{
throw;
}
}

Thread.Sleep(200);

var linkState = device.ReadRegister(FmcLinkController.LINKSTATE);
return (linkState & FmcLinkController.LINKSTATE_SL) != 0;
}
}
}
Loading