Skip to content

Commit

Permalink
Add support for UCLA Miniscope V4
Browse files Browse the repository at this point in the history
- This device needs some work arounds because its clock is completely
out of spec for the SERDES it uses. The back channel must be used to
configure a PLL on the camera in order to bump the PCLK rate before it
will like. For this reason, I mad ConfigureFmcLinkController.
CheckLinkState virtual so I could override its behavior with these steps
before checking the link state
- Added configuraiton and streaming functionality.
- There are few outstanding issues:
1. Something is wrong with the DS90UB9x configuration resulting in 1
missing column from each frame and imaging data that "slides" by one
column for each image produced
2. The axis map for the BNO needs to be set correctly
3. I was unable to verify that the EWL was functioning. It might be
fine, but I was unable to verify.
  • Loading branch information
jonnew committed Jun 24, 2024
1 parent 481a915 commit 2c8945a
Show file tree
Hide file tree
Showing 8 changed files with 565 additions and 1 deletion.
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 Imager { 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;
Imager.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 Imager;
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))
{
}
}
}
}
197 changes: 197 additions & 0 deletions OpenEphys.Onix/OpenEphys.Onix/ConfigureUclaMiniscopeV4Camera.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
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;
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));

uint shutterWidth = FrameRate switch
{
UclaMiniscopeV4FramesPerSecond.Fps10Hz => 10000,
UclaMiniscopeV4FramesPerSecond.Fps15Hz => 6667,
UclaMiniscopeV4FramesPerSecond.Fps20Hz => 5000,
UclaMiniscopeV4FramesPerSecond.Fps25Hz => 4000,
UclaMiniscopeV4FramesPerSecond.Fps30Hz => 3300,
_ => 3300
};

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);
device.WriteRegister(DS90UB9x.MARK,0);

// 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 alias = UclaMiniscopeV4.AtMegaAddress << 1;
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveID1, alias);
deserializer.WriteByte((uint)DS90UB9xDeserializerI2CRegister.SlaveAlias1, alias);

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

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

// 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;
if (ex.Number != FailureToWriteRegister)
{
throw;
}
}

Thread.Sleep(200);

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

0 comments on commit 2c8945a

Please sign in to comment.