From 762c37fda9a95e8cf03f98ed6c487b9134e51f70 Mon Sep 17 00:00:00 2001 From: Aaron Cuevas Lopez <aacuelo@teleco.upv.es> Date: Wed, 27 Apr 2016 01:54:12 +0200 Subject: [PATCH] Add Xillybus PCIe Rhythm test plugin --- .../Plugins/PCIeRhythm/PCIeRhythm.vcxproj | 139 + .../PCIeRhythm/PCIeRhythm.vcxproj.filters | 57 + Builds/VisualStudio2013/Plugins/Plugins.sln | 84 + Source/Plugins/PCIeRhythm/OpenEphysLib.cpp | 69 + Source/Plugins/PCIeRhythm/RHD2000Editor.cpp | 1562 +++++++++++ Source/Plugins/PCIeRhythm/RHD2000Editor.h | 399 +++ Source/Plugins/PCIeRhythm/RHD2000Thread.cpp | 2386 +++++++++++++++++ Source/Plugins/PCIeRhythm/RHD2000Thread.h | 274 ++ .../PCIeRhythm/rhythm-api/rhd2000PCIe.cpp | 974 +++++++ .../PCIeRhythm/rhythm-api/rhd2000PCIe.h | 164 ++ .../rhythm-api/rhd2000datablock.cpp | 390 +++ .../PCIeRhythm/rhythm-api/rhd2000datablock.h | 68 + .../rhythm-api/rhd2000registers.cpp | 1025 +++++++ .../PCIeRhythm/rhythm-api/rhd2000registers.h | 170 ++ 14 files changed, 7761 insertions(+) create mode 100644 Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj create mode 100644 Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj.filters create mode 100644 Source/Plugins/PCIeRhythm/OpenEphysLib.cpp create mode 100644 Source/Plugins/PCIeRhythm/RHD2000Editor.cpp create mode 100644 Source/Plugins/PCIeRhythm/RHD2000Editor.h create mode 100644 Source/Plugins/PCIeRhythm/RHD2000Thread.cpp create mode 100644 Source/Plugins/PCIeRhythm/RHD2000Thread.h create mode 100644 Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.cpp create mode 100644 Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.h create mode 100644 Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.cpp create mode 100644 Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.h create mode 100644 Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.cpp create mode 100644 Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.h diff --git a/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj new file mode 100644 index 000000000..93416781c --- /dev/null +++ b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{8F019559-89D4-4C33-BA0F-FF330C57BA2B}</ProjectGuid> + <RootNamespace>PCIeRhythm</RootNamespace> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v120</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Debug32.props" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Debug64.props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Release32.props" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\Plugin_Release64.props" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup /> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <SDLCheck>true</SDLCheck> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <SDLCheck>true</SDLCheck> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\OpenEphysLib.cpp" /> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.cpp" /> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.cpp" /> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.cpp" /> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.cpp" /> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.h" /> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.h" /> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.h" /> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.h" /> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj.filters b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj.filters new file mode 100644 index 000000000..c907b4318 --- /dev/null +++ b/Builds/VisualStudio2013/Plugins/PCIeRhythm/PCIeRhythm.vcxproj.filters @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + <Filter Include="Source Files\rhythm-api"> + <UniqueIdentifier>{8ec4462a-3223-46ca-a2f1-cf88ba3c80f6}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.cpp"> + <Filter>Source Files\rhythm-api</Filter> + </ClCompile> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.cpp"> + <Filter>Source Files\rhythm-api</Filter> + </ClCompile> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.cpp"> + <Filter>Source Files\rhythm-api</Filter> + </ClCompile> + <ClCompile Include="..\..\..\..\Source\Plugins\PCIeRhythm\OpenEphysLib.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Editor.h"> + <Filter>Source Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\RHD2000Thread.h"> + <Filter>Source Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000datablock.h"> + <Filter>Source Files\rhythm-api</Filter> + </ClInclude> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000registers.h"> + <Filter>Source Files\rhythm-api</Filter> + </ClInclude> + <ClInclude Include="..\..\..\..\Source\Plugins\PCIeRhythm\rhythm-api\rhd2000PCIe.h"> + <Filter>Source Files\rhythm-api</Filter> + </ClInclude> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/Builds/VisualStudio2013/Plugins/Plugins.sln b/Builds/VisualStudio2013/Plugins/Plugins.sln index b70f4cf73..34827585f 100644 --- a/Builds/VisualStudio2013/Plugins/Plugins.sln +++ b/Builds/VisualStudio2013/Plugins/Plugins.sln @@ -37,146 +37,230 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SpikeSorter", "SpikeSorter\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExampleProcessor", "ExampleProcessor\ExampleProcessor.vcxproj", "{767D282E-0BE5-4B35-874A-3B1ED925F06B}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PCIeRhythm", "PCIeRhythm\PCIeRhythm.vcxproj", "{8F019559-89D4-4C33-BA0F-FF330C57BA2B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Mixed Platforms = Debug|Mixed Platforms Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 + Release|Mixed Platforms = Release|Mixed Platforms Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41BD734E-4939-47AD-9714-9629538F7206}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {41BD734E-4939-47AD-9714-9629538F7206}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {41BD734E-4939-47AD-9714-9629538F7206}.Debug|Win32.ActiveCfg = Debug|Win32 {41BD734E-4939-47AD-9714-9629538F7206}.Debug|Win32.Build.0 = Debug|Win32 {41BD734E-4939-47AD-9714-9629538F7206}.Debug|x64.ActiveCfg = Debug|x64 {41BD734E-4939-47AD-9714-9629538F7206}.Debug|x64.Build.0 = Debug|x64 + {41BD734E-4939-47AD-9714-9629538F7206}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {41BD734E-4939-47AD-9714-9629538F7206}.Release|Mixed Platforms.Build.0 = Release|Win32 {41BD734E-4939-47AD-9714-9629538F7206}.Release|Win32.ActiveCfg = Release|Win32 {41BD734E-4939-47AD-9714-9629538F7206}.Release|Win32.Build.0 = Release|Win32 {41BD734E-4939-47AD-9714-9629538F7206}.Release|x64.ActiveCfg = Release|x64 {41BD734E-4939-47AD-9714-9629538F7206}.Release|x64.Build.0 = Release|x64 + {554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Win32.ActiveCfg = Debug|Win32 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|Win32.Build.0 = Debug|Win32 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|x64.ActiveCfg = Debug|x64 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Debug|x64.Build.0 = Debug|x64 + {554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Mixed Platforms.Build.0 = Release|Win32 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Win32.ActiveCfg = Release|Win32 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|Win32.Build.0 = Release|Win32 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|x64.ActiveCfg = Release|x64 {554C8744-32CD-427C-A9E5-BF9A44440CED}.Release|x64.Build.0 = Release|x64 + {7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Win32.ActiveCfg = Debug|Win32 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|Win32.Build.0 = Debug|Win32 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|x64.ActiveCfg = Debug|x64 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Debug|x64.Build.0 = Debug|x64 + {7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Mixed Platforms.Build.0 = Release|Win32 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Win32.ActiveCfg = Release|Win32 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|Win32.Build.0 = Release|Win32 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|x64.ActiveCfg = Release|x64 {7B23828E-559F-4AD2-B75D-D05786F6329C}.Release|x64.Build.0 = Release|x64 + {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Win32.ActiveCfg = Debug|Win32 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|Win32.Build.0 = Debug|Win32 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|x64.ActiveCfg = Debug|x64 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Debug|x64.Build.0 = Debug|x64 + {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Mixed Platforms.Build.0 = Release|Win32 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Win32.ActiveCfg = Release|Win32 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|Win32.Build.0 = Release|Win32 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|x64.ActiveCfg = Release|x64 {D5D58DAC-582B-4F39-9385-E1814D56BCA0}.Release|x64.Build.0 = Release|x64 + {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Win32.ActiveCfg = Debug|Win32 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|Win32.Build.0 = Debug|Win32 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|x64.ActiveCfg = Debug|x64 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Debug|x64.Build.0 = Debug|x64 + {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Mixed Platforms.Build.0 = Release|Win32 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Win32.ActiveCfg = Release|Win32 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|Win32.Build.0 = Release|Win32 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|x64.ActiveCfg = Release|x64 {AC9EBFBF-1599-40CF-8159-3F3AA7DCFAE9}.Release|x64.Build.0 = Release|x64 + {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Win32.ActiveCfg = Debug|Win32 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|Win32.Build.0 = Debug|Win32 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|x64.ActiveCfg = Debug|x64 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Debug|x64.Build.0 = Debug|x64 + {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Mixed Platforms.Build.0 = Release|Win32 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Win32.ActiveCfg = Release|Win32 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|Win32.Build.0 = Release|Win32 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|x64.ActiveCfg = Release|x64 {9D050DCC-52DF-429E-ABF0-EF0B64A9DA5A}.Release|x64.Build.0 = Release|x64 + {79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Win32.ActiveCfg = Debug|Win32 {79703461-0C53-417B-BEA2-B3903A41C123}.Debug|Win32.Build.0 = Debug|Win32 {79703461-0C53-417B-BEA2-B3903A41C123}.Debug|x64.ActiveCfg = Debug|x64 {79703461-0C53-417B-BEA2-B3903A41C123}.Debug|x64.Build.0 = Debug|x64 + {79703461-0C53-417B-BEA2-B3903A41C123}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {79703461-0C53-417B-BEA2-B3903A41C123}.Release|Mixed Platforms.Build.0 = Release|Win32 {79703461-0C53-417B-BEA2-B3903A41C123}.Release|Win32.ActiveCfg = Release|Win32 {79703461-0C53-417B-BEA2-B3903A41C123}.Release|Win32.Build.0 = Release|Win32 {79703461-0C53-417B-BEA2-B3903A41C123}.Release|x64.ActiveCfg = Release|x64 {79703461-0C53-417B-BEA2-B3903A41C123}.Release|x64.Build.0 = Release|x64 + {9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Win32.ActiveCfg = Debug|Win32 {9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|Win32.Build.0 = Debug|Win32 {9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|x64.ActiveCfg = Debug|x64 {9C33B87F-24D7-4952-91C0-44759C77F491}.Debug|x64.Build.0 = Debug|x64 + {9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Mixed Platforms.Build.0 = Release|Win32 {9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Win32.ActiveCfg = Release|Win32 {9C33B87F-24D7-4952-91C0-44759C77F491}.Release|Win32.Build.0 = Release|Win32 {9C33B87F-24D7-4952-91C0-44759C77F491}.Release|x64.ActiveCfg = Release|x64 {9C33B87F-24D7-4952-91C0-44759C77F491}.Release|x64.Build.0 = Release|x64 + {75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Win32.ActiveCfg = Debug|Win32 {75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|Win32.Build.0 = Debug|Win32 {75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|x64.ActiveCfg = Debug|x64 {75DEED7F-17D0-4805-9272-96288B79BA53}.Debug|x64.Build.0 = Debug|x64 + {75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Mixed Platforms.Build.0 = Release|Win32 {75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Win32.ActiveCfg = Release|Win32 {75DEED7F-17D0-4805-9272-96288B79BA53}.Release|Win32.Build.0 = Release|Win32 {75DEED7F-17D0-4805-9272-96288B79BA53}.Release|x64.ActiveCfg = Release|x64 {75DEED7F-17D0-4805-9272-96288B79BA53}.Release|x64.Build.0 = Release|x64 + {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Win32.ActiveCfg = Debug|Win32 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|Win32.Build.0 = Debug|Win32 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|x64.ActiveCfg = Debug|x64 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Debug|x64.Build.0 = Debug|x64 + {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Mixed Platforms.Build.0 = Release|Win32 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Win32.ActiveCfg = Release|Win32 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|Win32.Build.0 = Release|Win32 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|x64.ActiveCfg = Release|x64 {A29DFCB3-837D-491F-B9C4-3E0C93404128}.Release|x64.Build.0 = Release|x64 + {88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Win32.ActiveCfg = Debug|Win32 {88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|Win32.Build.0 = Debug|Win32 {88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|x64.ActiveCfg = Debug|x64 {88971DC8-9416-4229-AAB8-870988D9A81A}.Debug|x64.Build.0 = Debug|x64 + {88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Mixed Platforms.Build.0 = Release|Win32 {88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Win32.ActiveCfg = Release|Win32 {88971DC8-9416-4229-AAB8-870988D9A81A}.Release|Win32.Build.0 = Release|Win32 {88971DC8-9416-4229-AAB8-870988D9A81A}.Release|x64.ActiveCfg = Release|x64 {88971DC8-9416-4229-AAB8-870988D9A81A}.Release|x64.Build.0 = Release|x64 + {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Win32.ActiveCfg = Debug|Win32 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|Win32.Build.0 = Debug|Win32 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|x64.ActiveCfg = Debug|x64 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Debug|x64.Build.0 = Debug|x64 + {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Mixed Platforms.Build.0 = Release|Win32 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Win32.ActiveCfg = Release|Win32 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|Win32.Build.0 = Release|Win32 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|x64.ActiveCfg = Release|x64 {6A7972E7-89DD-45F3-BFA5-C50F9B701B7D}.Release|x64.Build.0 = Release|x64 + {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Win32.ActiveCfg = Debug|Win32 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|Win32.Build.0 = Debug|Win32 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|x64.ActiveCfg = Debug|x64 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Debug|x64.Build.0 = Debug|x64 + {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Mixed Platforms.Build.0 = Release|Win32 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Win32.ActiveCfg = Release|Win32 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|Win32.Build.0 = Release|Win32 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|x64.ActiveCfg = Release|x64 {11C301E8-35E7-48AE-978F-4AC651CEFC67}.Release|x64.Build.0 = Release|x64 + {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Win32.ActiveCfg = Debug|Win32 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|Win32.Build.0 = Debug|Win32 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|x64.ActiveCfg = Debug|x64 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Debug|x64.Build.0 = Debug|x64 + {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Mixed Platforms.Build.0 = Release|Win32 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Win32.ActiveCfg = Release|Win32 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|Win32.Build.0 = Release|Win32 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|x64.ActiveCfg = Release|x64 {3A753B88-4374-4456-93D8-D0FDC9DCACF3}.Release|x64.Build.0 = Release|x64 + {2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Win32.ActiveCfg = Debug|Win32 {2A290337-FC79-4987-8488-A71F29252DB0}.Debug|Win32.Build.0 = Debug|Win32 {2A290337-FC79-4987-8488-A71F29252DB0}.Debug|x64.ActiveCfg = Debug|x64 {2A290337-FC79-4987-8488-A71F29252DB0}.Debug|x64.Build.0 = Debug|x64 + {2A290337-FC79-4987-8488-A71F29252DB0}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {2A290337-FC79-4987-8488-A71F29252DB0}.Release|Mixed Platforms.Build.0 = Release|Win32 {2A290337-FC79-4987-8488-A71F29252DB0}.Release|Win32.ActiveCfg = Release|Win32 {2A290337-FC79-4987-8488-A71F29252DB0}.Release|Win32.Build.0 = Release|Win32 {2A290337-FC79-4987-8488-A71F29252DB0}.Release|x64.ActiveCfg = Release|x64 {2A290337-FC79-4987-8488-A71F29252DB0}.Release|x64.Build.0 = Release|x64 + {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Win32.ActiveCfg = Debug|Win32 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|Win32.Build.0 = Debug|Win32 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|x64.ActiveCfg = Debug|x64 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Debug|x64.Build.0 = Debug|x64 + {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Mixed Platforms.Build.0 = Release|Win32 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Win32.ActiveCfg = Release|Win32 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|Win32.Build.0 = Release|Win32 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|x64.ActiveCfg = Release|x64 {D0AEE4AA-68A7-4598-89E4-3BF5A5EBCA2A}.Release|x64.Build.0 = Release|x64 + {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|Win32.ActiveCfg = Debug|Win32 {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Debug|x64.ActiveCfg = Debug|x64 + {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Mixed Platforms.Build.0 = Release|Win32 {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Win32.ActiveCfg = Release|Win32 {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|x64.ActiveCfg = Release|x64 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Mixed Platforms.Build.0 = Debug|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Win32.Build.0 = Debug|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|x64.ActiveCfg = Debug|x64 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|x64.Build.0 = Debug|x64 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Mixed Platforms.ActiveCfg = Release|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Mixed Platforms.Build.0 = Release|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Win32.ActiveCfg = Release|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Win32.Build.0 = Release|Win32 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|x64.ActiveCfg = Release|x64 + {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Source/Plugins/PCIeRhythm/OpenEphysLib.cpp b/Source/Plugins/PCIeRhythm/OpenEphysLib.cpp new file mode 100644 index 000000000..a29697965 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/OpenEphysLib.cpp @@ -0,0 +1,69 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include <PluginInfo.h> +#include "RHD2000Thread.h" +#include <string> +#ifdef WIN32 +#include <Windows.h> +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +using namespace Plugin; +#define NUM_PLUGINS 1 + +extern "C" EXPORT void getLibInfo(Plugin::LibraryInfo* info) +{ + info->apiVersion = PLUGIN_API_VER; + info->name = "PCIe Rhythm interface"; + info->libVersion = 1; + info->numPlugins = NUM_PLUGINS; +} + +extern "C" EXPORT int getPluginInfo(int index, Plugin::PluginInfo* info) +{ + switch (index) + { + case 0: + info->type = Plugin::DatathreadPlugin; + info->dataThread.name = "PCIe Rhythm"; + info->dataThread.creator = &createDataThread<PCIeRhythm::RHD2000Thread>; + break; + default: + return -1; + break; + } + return 0; +} + +#ifdef WIN32 +BOOL WINAPI DllMain(IN HINSTANCE hDllHandle, + IN DWORD nReason, + IN LPVOID Reserved) +{ + return TRUE; +} + +#endif \ No newline at end of file diff --git a/Source/Plugins/PCIeRhythm/RHD2000Editor.cpp b/Source/Plugins/PCIeRhythm/RHD2000Editor.cpp new file mode 100644 index 000000000..e8292c064 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/RHD2000Editor.cpp @@ -0,0 +1,1562 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "RHD2000Editor.h" +#include <cmath> +#include "RHD2000Thread.h" +using namespace PCIeRhythm; + +#ifdef WIN32 +#if (_MSC_VER < 1800) //round doesn't exist on MSVC prior to 2013 version +inline double round(double x) +{ + return floor(x+0.5); +} +#endif +#endif + +FPGAchannelList::FPGAchannelList(GenericProcessor* proc_, Viewport* p, FPGAcanvas* c) : chainUpdate(false), viewport(p), canvas(c) +{ + proc = (SourceNode*)proc_; + channelComponents.clear(); + + numberingSchemeLabel = new Label("Numbering scheme:","Numbering scheme:"); + numberingSchemeLabel->setEditable(false); + numberingSchemeLabel->setBounds(10,10,150, 25); + numberingSchemeLabel->setColour(Label::textColourId,juce::Colours::white); + addAndMakeVisible(numberingSchemeLabel); + + numberingScheme = new ComboBox("numberingScheme"); + numberingScheme->addItem("Continuous",1); + numberingScheme->addItem("Per Stream",2); + numberingScheme->setBounds(160,10,100,25); + numberingScheme->addListener(this); + numberingScheme->setSelectedId(1, dontSendNotification); + addAndMakeVisible(numberingScheme); + + impedanceButton = new UtilityButton("Measure Impedance", Font("Default", 13, Font::plain)); + impedanceButton->setRadius(3); + impedanceButton->setBounds(280,10,140,25); + impedanceButton->addListener(this); + addAndMakeVisible(impedanceButton); + + RHD2000Editor* e = static_cast<RHD2000Editor*>(proc->getEditor()); + saveImpedanceButton = new ToggleButton("Save impedance measurements"); + saveImpedanceButton->setBounds(430,10,110,25); + saveImpedanceButton->setToggleState(e->getSaveImpedance(),dontSendNotification); + saveImpedanceButton->addListener(this); + addAndMakeVisible(saveImpedanceButton); + + autoMeasureButton = new ToggleButton("Measure impedance at acquisition start"); + autoMeasureButton->setBounds(550,10,150,25); + autoMeasureButton->setToggleState(e->getAutoMeasureImpedance(),dontSendNotification); + autoMeasureButton->addListener(this); + addAndMakeVisible(autoMeasureButton); + + gains.clear(); + gains.add(0.01); + gains.add(0.1); + gains.add(1); + gains.add(2); + gains.add(5); + gains.add(10); + gains.add(20); + gains.add(50); + gains.add(100); + gains.add(500); + gains.add(1000); + + + update(); +} + +FPGAchannelList::~FPGAchannelList() +{ + +} + +void FPGAchannelList::paint(Graphics& g) +{ +} + +void FPGAchannelList::buttonClicked(Button* btn) +{ + RHD2000Editor* p = (RHD2000Editor*)proc->getEditor(); + if (btn == impedanceButton) + { + p->measureImpedance(); + } + else if (btn == saveImpedanceButton) + { + p->setSaveImpedance(btn->getToggleState()); + } + else if (btn == autoMeasureButton) + { + p->setAutoMeasureImpedance(btn->getToggleState()); + } +} + +void FPGAchannelList::update() +{ + // const int columnWidth = 330; + const int columnWidth = 250; + // Query processor for number of channels, types, gains, etc... and update the UI + channelComponents.clear(); + staticLabels.clear(); + + RHD2000Thread* thread = (RHD2000Thread*)proc->getThread(); + ChannelType type; + + // find out which streams are active. + bool hsActive[MAX_NUM_HEADSTAGES+1]; + //bool adcActive = false; + int numActiveHeadstages = 0; + int hsColumn[MAX_NUM_HEADSTAGES + 1]; + int numChannelsPerHeadstage[MAX_NUM_HEADSTAGES + 1]; + chainUpdate = false; + + for (int k = 0; k<MAX_NUM_HEADSTAGES; k++) + { + if (thread->isHeadstageEnabled(k)) + { + numChannelsPerHeadstage[k] = thread->getActiveChannelsInHeadstage(k); + hsActive[k] = true; + hsColumn[k] = numActiveHeadstages*columnWidth; + numActiveHeadstages++; + } + else + { + numChannelsPerHeadstage[k] = 0; + hsActive[k] = false; + hsColumn[k] = 0; + } + } + + if (thread->getNumAdcOutputs() > 0) + { + numChannelsPerHeadstage[MAX_NUM_HEADSTAGES] = thread->getNumAdcOutputs(); + hsActive[MAX_NUM_HEADSTAGES] = true; + hsColumn[MAX_NUM_HEADSTAGES] = numActiveHeadstages*columnWidth; + numActiveHeadstages++; + } + else + { + numChannelsPerHeadstage[MAX_NUM_HEADSTAGES] = 0; + hsActive[MAX_NUM_HEADSTAGES] = false; + hsColumn[MAX_NUM_HEADSTAGES] = 0; + } + + StringArray streamNames; + streamNames.add("Port A1"); + streamNames.add("Port A2"); + streamNames.add("Port B1"); + streamNames.add("Port B2"); + streamNames.add("Port C1"); + streamNames.add("Port C2"); + streamNames.add("Port D1"); + streamNames.add("Port D2"); + streamNames.add("ADC"); + + for (int k = 0; k < MAX_NUM_HEADSTAGES + 1; k++) + { + if (hsActive[k]) + { + Label* lbl = new Label(streamNames[k],streamNames[k]); + lbl->setEditable(false); + lbl->setBounds(10+hsColumn[k],40,columnWidth, 25); + lbl->setJustificationType(juce::Justification::centred); + lbl->setColour(Label::textColourId,juce::Colours::white); + staticLabels.add(lbl); + addAndMakeVisible(lbl); + + } + + } + + for (int k = 0; k < MAX_NUM_HEADSTAGES + 1; k++) + { + if (hsActive[k]) + { + for (int ch = 0; ch < numChannelsPerHeadstage[k]+ (k < MAX_NUM_HEADSTAGES ? 3 : 0); ch++) + { + int channelGainIndex = 1; + int realChan = thread->getChannelFromHeadstage(k, ch); + float ch_gain = proc->channels[realChan]->getBitVolts() / proc->getBitVolts(proc->channels[realChan]); + for (int j = 0; j < gains.size(); j++) + { + if (fabs(gains[j] - ch_gain) < 1e-3) + { + channelGainIndex = j; + break; + } + } + if (k < MAX_NUM_HEADSTAGES) + type = ch < numChannelsPerHeadstage[k] ? HEADSTAGE_CHANNEL : AUX_CHANNEL; + else + type = ADC_CHANNEL; + + FPGAchannelComponent* comp = new FPGAchannelComponent(this, realChan, channelGainIndex + 1, thread->getChannelName(realChan), gains,type); + comp->setBounds(10 + hsColumn[k], 70 + ch * 22, columnWidth, 22); + comp->setUserDefinedData(k); + addAndMakeVisible(comp); + channelComponents.add(comp); + } + } + } + + + StringArray ttlNames; + proc->getEventChannelNames(ttlNames); + // add buttons for TTL channels + for (int k=0; k<ttlNames.size(); k++) + { + FPGAchannelComponent* comp = new FPGAchannelComponent(this,k, -1, ttlNames[k],gains,EVENT_CHANNEL); + comp->setBounds(10+numActiveHeadstages*columnWidth,70+k*22,columnWidth,22); + comp->setUserDefinedData(k); + addAndMakeVisible(comp); + channelComponents.add(comp); + } + + Label* lbl = new Label("TTL Events","TTL Events"); + lbl->setEditable(false); + lbl->setBounds(numActiveHeadstages*columnWidth,40,columnWidth, 25); + lbl->setJustificationType(juce::Justification::centred); + lbl->setColour(Label::textColourId,juce::Colours::white); + staticLabels.add(lbl); + addAndMakeVisible(lbl); + + chainUpdate = true; +} + +void FPGAchannelList::disableAll() +{ + for (int k=0; k<channelComponents.size(); k++) + { + channelComponents[k]->disableEdit(); + } + impedanceButton->setEnabled(false); + saveImpedanceButton->setEnabled(false); + autoMeasureButton->setEnabled(false); + numberingScheme->setEnabled(false); +} + +void FPGAchannelList::enableAll() +{ + for (int k=0; k<channelComponents.size(); k++) + { + channelComponents[k]->enableEdit(); + } + impedanceButton->setEnabled(true); + saveImpedanceButton->setEnabled(true); + autoMeasureButton->setEnabled(true); + numberingScheme->setEnabled(true); +} + +void FPGAchannelList::setNewGain(int channel, float gain) +{ + RHD2000Thread* thread = (RHD2000Thread*)proc->getThread(); + thread->modifyChannelGain(channel, gain); + if (chainUpdate) + proc->requestChainUpdate(); +} + +void FPGAchannelList::setNewName(int channel, String newName) +{ + RHD2000Thread* thread = (RHD2000Thread*)proc->getThread(); + thread->modifyChannelName(channel, newName); + if (chainUpdate) + proc->requestChainUpdate(); +} + +void FPGAchannelList::updateButtons() +{ +} + +int FPGAchannelList::getNumChannels() +{ + return 0; +} + +void FPGAchannelList::comboBoxChanged(ComboBox* b) +{ + if (b == numberingScheme) + { + SourceNode* p = (SourceNode*)proc; + RHD2000Thread* thread = (RHD2000Thread*)p->getThread(); + int scheme = numberingScheme->getSelectedId(); + thread->setDefaultNamingScheme(scheme); + update(); + p->requestChainUpdate(); + } +} + +void FPGAchannelList::updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase) +{ + int i = 0; + for (int k = 0; k < streams.size(); k++) + { + if (i >= channelComponents.size()) + break; //little safety + + if (channelComponents[i]->type != HEADSTAGE_CHANNEL) + { + k--; + } + else + { + channelComponents[i]->setImpedanceValues(magnitude[k], phase[k]); + } + i++; + } + +} + + +/****************************************************/ +FPGAchannelComponent::FPGAchannelComponent(FPGAchannelList* cl, int ch, int gainIndex_, String N, Array<float> gains_, ChannelType type_) : +type(type_), gains(gains_), channelList(cl), channel(ch), name(N), gainIndex(gainIndex_) +{ + Font f = Font("Small Text", 13, Font::plain); + + staticLabel = new Label("Channel","Channel"); + staticLabel->setFont(f); + staticLabel->setEditable(false); + addAndMakeVisible(staticLabel); + + editName = new Label(name,name); + editName->setFont(f); + editName->setEditable(true); + editName->setColour(Label::backgroundColourId,juce::Colours::lightgrey); + editName->addListener(this); + addAndMakeVisible(editName); +/* if (gainIndex > 0) + { + + gainComboBox = new ComboBox("Gains"); + for (int k=0; k<gains.size(); k++) + { + if (gains[k] < 1) + { + gainComboBox->addItem("x"+String(gains[k],2),k+1); + } + else + { + gainComboBox->addItem("x"+String((int)gains[k]),k+1); + } + } + gainComboBox->setSelectedId(gainIndex, sendNotificationSync); + gainComboBox->addListener(this); + addAndMakeVisible(gainComboBox); + } + else + {*/ + gainComboBox = nullptr; + //} + + if (type == HEADSTAGE_CHANNEL) + { + impedance = new Label("Impedance","? Ohm"); + impedance->setFont(Font("Default", 13, Font::plain)); + impedance->setEditable(false); + addAndMakeVisible(impedance); + } + else + { + impedance = nullptr; + } +} +FPGAchannelComponent::~FPGAchannelComponent() +{ + +} + +void FPGAchannelComponent::setImpedanceValues(float mag, float phase) +{ + if (impedance != nullptr) + { + if (mag > 10000) + impedance->setText(String(mag/1e6,2)+" mOhm, "+String((int)phase) + " deg",juce::NotificationType::dontSendNotification); + else if (mag > 1000) + impedance->setText(String(mag/1e3,0)+" kOhm, "+String((int)phase) + " deg" ,juce::NotificationType::dontSendNotification); + else + impedance->setText(String(mag,0)+" Ohm, "+String((int)phase) + " deg" ,juce::NotificationType::dontSendNotification); + } + else + { + + } +} + +void FPGAchannelComponent::comboBoxChanged(ComboBox* comboBox) +{ + if (comboBox == gainComboBox) + { + int newGainIndex = gainComboBox->getSelectedId(); + float mult = gains[newGainIndex-1]; + float bitvolts = channelList->proc->getBitVolts(channelList->proc->channels[channel]); + channelList->setNewGain(channel, mult*bitvolts); + } +} +void FPGAchannelComponent::labelTextChanged(Label* lbl) +{ + // channel name change + String newName = lbl->getText(); + channelList->setNewName(channel, newName); +} + +void FPGAchannelComponent::disableEdit() +{ + editName->setEnabled(false); +} + +void FPGAchannelComponent::enableEdit() +{ + editName->setEnabled(true); +} + +void FPGAchannelComponent::buttonClicked(Button* btn) +{ +} + +void FPGAchannelComponent::setUserDefinedData(int d) +{ +} + +int FPGAchannelComponent::getUserDefinedData() +{ + return 0; +} + +void FPGAchannelComponent::resized() +{ + editName->setBounds(0,0,90,20); + if (gainComboBox != nullptr) + { + gainComboBox->setBounds(100,0,70,20); + } + if (impedance != nullptr) + { + // impedance->setBounds(180,0,130,20); + impedance->setBounds(100, 0, 130, 20); + } + +} + + + +/**********************************************/ + +FPGAcanvas::FPGAcanvas(GenericProcessor* n) +{ + processor = (SourceNode*)n; + channelsViewport = new Viewport(); + channelList = new FPGAchannelList(processor, channelsViewport, this); + channelsViewport->setViewedComponent(channelList, false); + channelsViewport->setScrollBarsShown(true, true); + addAndMakeVisible(channelsViewport); + + resized(); + update(); +} + +FPGAcanvas::~FPGAcanvas() +{ +} + +void FPGAcanvas::setParameter(int x, float f) +{ + +} + +void FPGAcanvas::setParameter(int a, int b, int c, float d) +{ +} + +void FPGAcanvas::paint(Graphics& g) +{ + g.fillAll(Colours::grey); + +} + +void FPGAcanvas::refresh() +{ + repaint(); +} + +void FPGAcanvas::refreshState() +{ + resized(); +} + + +void FPGAcanvas::beginAnimation() +{ +} + +void FPGAcanvas::endAnimation() +{ +} + +void FPGAcanvas::update() +{ + // create channel buttons (name, gain, recording, impedance, ... ?) + channelList->update(); + if (static_cast<RHD2000Thread*>(processor->getThread())->isAcquisitionActive()) + { + channelList->disableAll(); + } +} + +void FPGAcanvas::resized() +{ + //int screenWidth = getWidth(); + //int screenHeight = getHeight(); + + int scrollBarThickness = channelsViewport->getScrollBarThickness(); + int numChannels = 35; // max channels per stream? (32+3)*2 + + channelsViewport->setBounds(0,0,getWidth(),getHeight()); + channelList->setBounds(0,0,getWidth()-scrollBarThickness, 200+22*numChannels); +} + +void FPGAcanvas::buttonClicked(Button* button) +{ +} + +void FPGAcanvas::updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase) +{ + channelList->updateImpedance(streams, channels, magnitude, phase); +} + +/***********************************************************************/ + +RHD2000Editor::RHD2000Editor(GenericProcessor* parentNode, + RHD2000Thread* board_, + bool useDefaultParameterEditors + ) + : VisualizerEditor(parentNode, useDefaultParameterEditors), board(board_) +{ + canvas = nullptr; + desiredWidth = 340; + tabText = "FPGA"; + measureWhenRecording = false; + saveImpedances = false; + + impedanceData = new ImpedanceData(); + impedanceData->valid = false; + + // add headstage-specific controls (currently just an enable/disable button) + for (int i = 0; i < 4; i++) + { + HeadstageOptionsInterface* hsOptions = new HeadstageOptionsInterface(board, this, i); + headstageOptionsInterfaces.add(hsOptions); + addAndMakeVisible(hsOptions); + hsOptions->setBounds(3, 28+i*20, 70, 18); + } + + // add sample rate selection + sampleRateInterface = new SampleRateInterface(board, this); + addAndMakeVisible(sampleRateInterface); + sampleRateInterface->setBounds(80, 25, 110, 50); + + // add Bandwidth selection + bandwidthInterface = new BandwidthInterface(board, this); + addAndMakeVisible(bandwidthInterface); + bandwidthInterface->setBounds(80, 58, 80, 50); + + // add DSP selection + // dspInterface = new DSPInterface(board, this); + // addAndMakeVisible(dspInterface); + // dspInterface->setBounds(80, 58, 80, 50); + + // add rescan button + rescanButton = new UtilityButton("RESCAN", Font("Small Text", 13, Font::plain)); + rescanButton->setRadius(3.0f); + rescanButton->setBounds(6, 108,65,18); + rescanButton->addListener(this); + rescanButton->setTooltip("Check for connected headstages"); + addAndMakeVisible(rescanButton); + + for (int i = 0; i < 2; i++) + { + ElectrodeButton* button = new ElectrodeButton(-1); + electrodeButtons.add(button); + + button->setBounds(200+i*25, 40, 25, 15); + button->setChannelNum(-1); + button->setToggleState(false, dontSendNotification); + button->setRadioGroupId(999); + + addAndMakeVisible(button); + button->addListener(this); + + if (i == 0) + { + button->setTooltip("Audio monitor left channel"); + } + else + { + button->setTooltip("Audio monitor right channel"); + } + } + + audioLabel = new Label("audio label", "Audio out"); + audioLabel->setBounds(190,25,75,15); + audioLabel->setFont(Font("Small Text", 10, Font::plain)); + audioLabel->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(audioLabel); + + // add HW audio parameter selection + audioInterface = new AudioInterface(board, this); + addAndMakeVisible(audioInterface); + audioInterface->setBounds(179, 58, 70, 50); + + clockInterface = new ClockDivideInterface(board, this); + addAndMakeVisible(clockInterface); + clockInterface->setBounds(179, 82, 70, 50); + + adcButton = new UtilityButton("ADC 1-8", Font("Small Text", 13, Font::plain)); + adcButton->setRadius(3.0f); + adcButton->setBounds(179,108,70,18); + adcButton->addListener(this); + adcButton->setClickingTogglesState(true); + adcButton->setTooltip("Enable/disable ADC channels"); + addAndMakeVisible(adcButton); + + ledButton = new UtilityButton("LED", Font("Very Small Text", 13, Font::plain)); + ledButton->setRadius(3.0f); + ledButton->setBounds(140, 108, 30, 18); + ledButton->addListener(this); + ledButton->setClickingTogglesState(true); + ledButton->setTooltip("Enable/disable board LEDs"); + addAndMakeVisible(ledButton); + ledButton->setToggleState(true, dontSendNotification); + + // add DSP Offset Button + dspoffsetButton = new UtilityButton("DSP", Font("Very Small Text", 13, Font::plain)); + dspoffsetButton->setRadius(3.0f); // sets the radius of the button's corners + dspoffsetButton->setBounds(80, 108,30,18); // sets the x position, y position, width, and height of the button + dspoffsetButton->addListener(this); + dspoffsetButton->setClickingTogglesState(true); // makes the button toggle its state when clicked + dspoffsetButton->setTooltip("Enable/disable DSP offset removal"); + addAndMakeVisible(dspoffsetButton); // makes the button a child component of the editor and makes it visible + dspoffsetButton->setToggleState(true, dontSendNotification); + + // add DSP Frequency Selection field + dspInterface = new DSPInterface(board, this); + addAndMakeVisible(dspInterface); + dspInterface->setBounds(110, 108, 30, 50); + + ttlSettleLabel = new Label("TTL Settle","TTL Settle"); + ttlSettleLabel->setFont(Font("Small Text", 11, Font::plain)); + ttlSettleLabel->setBounds(255,80,100,20); + ttlSettleLabel->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(ttlSettleLabel); + + + ttlSettleCombo = new ComboBox("FastSettleComboBox"); + ttlSettleCombo->setBounds(260,100,60,18); + ttlSettleCombo->addListener(this); + ttlSettleCombo->addItem("-",1); + for (int k=0; k<8; k++) + { + ttlSettleCombo->addItem("TTL"+String(1+k),2+k); + } + ttlSettleCombo->setSelectedId(1, sendNotification); + addAndMakeVisible(ttlSettleCombo); + + dacTTLButton = new UtilityButton("DAC TTL", Font("Small Text", 13, Font::plain)); + dacTTLButton->setRadius(3.0f); + dacTTLButton->setBounds(260,25,65,18); + dacTTLButton->addListener(this); + dacTTLButton->setClickingTogglesState(true); + dacTTLButton->setTooltip("Enable/disable DAC Threshold TTL Output"); + addAndMakeVisible(dacTTLButton); + + dacHPFlabel = new Label("DAC HPF","DAC HPF"); + dacHPFlabel->setFont(Font("Small Text", 11, Font::plain)); + dacHPFlabel->setBounds(260,42,100,20); + dacHPFlabel->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(dacHPFlabel); + + dacHPFcombo = new ComboBox("dacHPFCombo"); + dacHPFcombo->setBounds(260,60,60,18); + dacHPFcombo->addListener(this); + dacHPFcombo->addItem("OFF",1); + int HPFvalues[10] = {50,100,200,300,400,500,600,700,800,900}; + for (int k=0; k<10; k++) + { + dacHPFcombo->addItem(String(HPFvalues[k])+" Hz",2+k); + } + dacHPFcombo->setSelectedId(1, sendNotification); + addAndMakeVisible(dacHPFcombo); + +} + +RHD2000Editor::~RHD2000Editor() +{ + +} + +void RHD2000Editor::scanPorts() +{ + rescanButton->triggerClick(); +} + +void RHD2000Editor::measureImpedance() +{ + impedanceData->valid = false; + board->runImpedanceTest(impedanceData); +} + +void RHD2000Editor::handleAsyncUpdate() +{ + if (!impedanceData->valid) + return; + if (canvas == nullptr) + VisualizerEditor::canvas = createNewCanvas(); + // update components... + canvas->updateImpedance(impedanceData->streams, impedanceData->channels, impedanceData->magnitudes, impedanceData->phases); + if (saveImpedances) + { + CoreServices::RecordNode::createNewrecordingDir(); + + String path(CoreServices::RecordNode::getRecordingPath().getFullPathName() + + File::separatorString + "impedance_measurement.xml"); + std::cout << "Saving impedance measurements in " << path << "\n"; + File file(path); + + if (!file.getParentDirectory().exists()) + file.getParentDirectory().createDirectory(); + + XmlDocument doc(file); + ScopedPointer<XmlElement> xml = new XmlElement("CHANNEL_IMPEDANCES"); + for (int i = 0; i < impedanceData->channels.size(); i++) + { + XmlElement* chan = new XmlElement("CHANNEL"); + chan->setAttribute("name",board->getChannelName(i)); + chan->setAttribute("stream", impedanceData->streams[i]); + chan->setAttribute("channel_number", impedanceData->channels[i]); + chan->setAttribute("magnitude", impedanceData->magnitudes[i]); + chan->setAttribute("phase", impedanceData->phases[i]); + xml->addChildElement(chan); + } + xml->writeToFile(file,String::empty); + } + +} + +void RHD2000Editor::setSaveImpedance(bool en) +{ + saveImpedances = en; +} + +void RHD2000Editor::setAutoMeasureImpedance(bool en) +{ + measureWhenRecording = en; +} + +bool RHD2000Editor::getSaveImpedance() +{ + return saveImpedances; +} + +bool RHD2000Editor::getAutoMeasureImpedance() +{ + return measureWhenRecording; +} + +void RHD2000Editor::comboBoxChanged(ComboBox* comboBox) +{ + if (comboBox == ttlSettleCombo) + { + int selectedChannel = ttlSettleCombo->getSelectedId(); + if (selectedChannel == 1) + { + board->setFastTTLSettle(false,0); + } + else + { + board->setFastTTLSettle(true,selectedChannel-2); + } + } + else if (comboBox == dacHPFcombo) + { + int selection = dacHPFcombo->getSelectedId(); + if (selection == 1) + { + board->setDAChpf(100,false); + } + else + { + int HPFvalues[10] = {50,100,200,300,400,500,600,700,800,900}; + board->setDAChpf(HPFvalues[selection-2],true); + } + } +} + + +void RHD2000Editor::buttonEvent(Button* button) +{ + if (button == rescanButton && !acquisitionIsActive) + { + board->scanPorts(); + + for (int i = 0; i < 4; i++) + { + headstageOptionsInterfaces[i]->checkEnabledState(); + } + // board->updateChannelNames(); + CoreServices::updateSignalChain(this); + } + else if (button == electrodeButtons[0]) + { + channelSelector->setRadioStatus(true); + } + else if (button == electrodeButtons[1]) + { + channelSelector->setRadioStatus(true); + } + else if (button == adcButton && !acquisitionIsActive) + { + board->enableAdcs(button->getToggleState()); + // board->updateChannelNames(); + std::cout << "ADC Button toggled" << "\n"; + CoreServices::updateSignalChain(this); + std::cout << "Editor visible." << "\n"; + } + else if (button == dacTTLButton) + { + board->setTTLoutputMode(dacTTLButton->getToggleState()); + } + else if (button == dspoffsetButton && !acquisitionIsActive) + { + std::cout << "DSP offset " << button->getToggleState() << "\n"; + board->setDSPOffset(button->getToggleState()); + } + else if (button == ledButton) + { + board->enableBoardLeds(button->getToggleState()); + } + else + { + VisualizerEditor::buttonEvent(button); + } + +} + +void RHD2000Editor::channelChanged (int channel, bool /*newState*/) +{ + for (int i = 0; i < 2; i++) + { + if (electrodeButtons[i]->getToggleState()) + { + electrodeButtons[i]->setChannelNum (channel); + electrodeButtons[i]->repaint(); + board->setDACchannel (i, channel); + } + } +} + +void RHD2000Editor::startAcquisition() +{ + if (measureWhenRecording) + measureImpedance(); + + channelSelector->startAcquisition(); + + rescanButton->setEnabledState(false); + adcButton->setEnabledState(false); + dspoffsetButton-> setEnabledState(false); + acquisitionIsActive = true; + if (canvas != nullptr) + canvas->channelList->disableAll(); + //canvas->channelList->setEnabled(false); +} + +void RHD2000Editor::stopAcquisition() +{ + + channelSelector->stopAcquisition(); + + rescanButton->setEnabledState(true); + adcButton->setEnabledState(true); + dspoffsetButton-> setEnabledState(true); + + acquisitionIsActive = false; + if (canvas != nullptr) + canvas->channelList->enableAll(); + // canvas->channelList->setEnabled(true); +} + +void RHD2000Editor::saveCustomParameters(XmlElement* xml) +{ + xml->setAttribute("SampleRate", sampleRateInterface->getSelectedId()); + xml->setAttribute("LowCut", bandwidthInterface->getLowerBandwidth()); + xml->setAttribute("HighCut", bandwidthInterface->getUpperBandwidth()); + xml->setAttribute("ADCsOn", adcButton->getToggleState()); + xml->setAttribute("SampleRate", sampleRateInterface->getSelectedId()); + xml->setAttribute("LowCut", bandwidthInterface->getLowerBandwidth()); + xml->setAttribute("HighCut", bandwidthInterface->getUpperBandwidth()); + xml->setAttribute("ADCsOn", adcButton->getToggleState()); + xml->setAttribute("AudioOutputL", electrodeButtons[0]->getChannelNum()); + xml->setAttribute("AudioOutputR", electrodeButtons[1]->getChannelNum()); + xml->setAttribute("NoiseSlicer", audioInterface->getNoiseSlicerLevel()); + xml->setAttribute("TTLFastSettle", ttlSettleCombo->getSelectedId()); + xml->setAttribute("DAC_TTL", dacTTLButton->getToggleState()); + xml->setAttribute("DAC_HPF", dacHPFcombo->getSelectedId()); + xml->setAttribute("DSPOffset", dspoffsetButton->getToggleState()); + xml->setAttribute("DSPCutoffFreq", dspInterface->getDspCutoffFreq()); + xml->setAttribute("save_impedance_measurements",saveImpedances); + xml->setAttribute("auto_measure_impedances",measureWhenRecording); + xml->setAttribute("LEDs", ledButton->getToggleState()); + xml->setAttribute("ClockDivideRatio", clockInterface->getClockDivideRatio()); +} + +void RHD2000Editor::loadCustomParameters(XmlElement* xml) +{ + + sampleRateInterface->setSelectedId(xml->getIntAttribute("SampleRate")); + bandwidthInterface->setLowerBandwidth(xml->getDoubleAttribute("LowCut")); + bandwidthInterface->setUpperBandwidth(xml->getDoubleAttribute("HighCut")); + adcButton->setToggleState(xml->getBoolAttribute("ADCsOn"), sendNotification); + //electrodeButtons[0]->setChannelNum(xml->getIntAttribute("AudioOutputL")); + //board->assignAudioOut(0, xml->getIntAttribute("AudioOutputL")); + //electrodeButtons[1]->setChannelNum(xml->getIntAttribute("AudioOutputR")); + //board->assignAudioOut(1, xml->getIntAttribute("AudioOutputR")); + audioInterface->setNoiseSlicerLevel(xml->getIntAttribute("NoiseSlicer")); + ttlSettleCombo->setSelectedId(xml->getIntAttribute("TTLFastSettle")); + dacTTLButton->setToggleState(xml->getBoolAttribute("DAC_TTL"), sendNotification); + dacHPFcombo->setSelectedId(xml->getIntAttribute("DAC_HPF")); + dspoffsetButton->setToggleState(xml->getBoolAttribute("DSPOffset"), sendNotification); + dspInterface->setDspCutoffFreq(xml->getDoubleAttribute("DSPCutoffFreq")); + saveImpedances = xml->getBoolAttribute("save_impedance_measurements"); + measureWhenRecording = xml->getBoolAttribute("auto_measure_impedances"); + ledButton->setToggleState(xml->getBoolAttribute("LEDs", true),sendNotification); + clockInterface->setClockDivideRatio(xml->getIntAttribute("ClockDivideRatio")); +} + + +Visualizer* RHD2000Editor::createNewCanvas() +{ + GenericProcessor* processor = (GenericProcessor*) getProcessor(); + canvas= new FPGAcanvas(processor); + //ActionListener* listener = (ActionListener*) canvas; + //getUIComponent()->registerAnimatedComponent(listener); + return canvas; +} + +// Bandwidth Options -------------------------------------------------------------------- + +BandwidthInterface::BandwidthInterface(RHD2000Thread* board_, + RHD2000Editor* editor_) : + board(board_), editor(editor_) +{ + + name = "Bandwidth"; + + lastHighCutString = "7500"; + lastLowCutString = "1"; + + actualUpperBandwidth = 7500.0f; + actualLowerBandwidth = 1.0f; + + upperBandwidthSelection = new Label("UpperBandwidth",lastHighCutString); // this is currently set in RHD2000Thread, the cleaner would be to set it here again + upperBandwidthSelection->setEditable(true,false,false); + upperBandwidthSelection->addListener(this); + upperBandwidthSelection->setBounds(30,30,60,20); + upperBandwidthSelection->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(upperBandwidthSelection); + + + lowerBandwidthSelection = new Label("LowerBandwidth",lastLowCutString); + lowerBandwidthSelection->setEditable(true,false,false); + lowerBandwidthSelection->addListener(this); + lowerBandwidthSelection->setBounds(25,10,60,20); + lowerBandwidthSelection->setColour(Label::textColourId, Colours::darkgrey); + + addAndMakeVisible(lowerBandwidthSelection); + + + +} + +BandwidthInterface::~BandwidthInterface() +{ + +} + + +void BandwidthInterface::labelTextChanged(Label* label) +{ + + if (!(editor->acquisitionIsActive) && board->foundInputSource()) + { + if (label == upperBandwidthSelection) + { + + Value val = label->getTextValue(); + double requestedValue = double(val.getValue()); + + if (requestedValue < 100.0 || requestedValue > 20000.0 || requestedValue < lastLowCutString.getFloatValue()) + { + CoreServices::sendStatusMessage("Value out of range."); + + label->setText(lastHighCutString, dontSendNotification); + + return; + } + + actualUpperBandwidth = board->setUpperBandwidth(requestedValue); + + std::cout << "Setting Upper Bandwidth to " << requestedValue << "\n"; + std::cout << "Actual Upper Bandwidth: " << actualUpperBandwidth << "\n"; + label->setText(String(round(actualUpperBandwidth*10.f)/10.f), dontSendNotification); + + } + else + { + + Value val = label->getTextValue(); + double requestedValue = double(val.getValue()); + + if (requestedValue < 0.1 || requestedValue > 500.0 || requestedValue > lastHighCutString.getFloatValue()) + { + CoreServices::sendStatusMessage("Value out of range."); + + label->setText(lastLowCutString, dontSendNotification); + + return; + } + + actualLowerBandwidth = board->setLowerBandwidth(requestedValue); + + std::cout << "Setting Lower Bandwidth to " << requestedValue << "\n"; + std::cout << "Actual Lower Bandwidth: " << actualLowerBandwidth << "\n"; + + label->setText(String(round(actualLowerBandwidth*10.f)/10.f), dontSendNotification); + } + } + else if (editor->acquisitionIsActive) + { + CoreServices::sendStatusMessage("Can't change bandwidth while acquisition is active!"); + if (label == upperBandwidthSelection) + label->setText(lastHighCutString, dontSendNotification); + else + label->setText(lastLowCutString, dontSendNotification); + return; + } + +} + +void BandwidthInterface::setLowerBandwidth(double value) +{ + actualLowerBandwidth = board->setLowerBandwidth(value); + lowerBandwidthSelection->setText(String(round(actualLowerBandwidth*10.f)/10.f), dontSendNotification); +} + +void BandwidthInterface::setUpperBandwidth(double value) +{ + actualUpperBandwidth = board->setUpperBandwidth(value); + upperBandwidthSelection->setText(String(round(actualUpperBandwidth*10.f)/10.f), dontSendNotification); +} + +double BandwidthInterface::getLowerBandwidth() +{ + return actualLowerBandwidth; +} + +double BandwidthInterface::getUpperBandwidth() +{ + return actualUpperBandwidth; +} + + +void BandwidthInterface::paint(Graphics& g) +{ + + g.setColour(Colours::darkgrey); + + g.setFont(Font("Small Text",10,Font::plain)); + + g.drawText(name, 0, 0, 200, 15, Justification::left, false); + + g.drawText("Low: ", 0, 10, 200, 20, Justification::left, false); + + g.drawText("High: ", 0, 30, 200, 20, Justification::left, false); + +} + +// Sample rate Options -------------------------------------------------------------------- + +SampleRateInterface::SampleRateInterface(RHD2000Thread* board_, + RHD2000Editor* editor_) : + board(board_), editor(editor_) +{ + + name = "Sample Rate"; + + sampleRateOptions.add("1.00 kS/s"); + sampleRateOptions.add("1.25 kS/s"); + sampleRateOptions.add("1.50 kS/s"); + sampleRateOptions.add("2.00 kS/s"); + sampleRateOptions.add("2.50 kS/s"); + sampleRateOptions.add("3.00 kS/s"); + sampleRateOptions.add("3.33 kS/s"); + sampleRateOptions.add("4.00 kS/s"); + sampleRateOptions.add("5.00 kS/s"); + sampleRateOptions.add("6.25 kS/s"); + sampleRateOptions.add("8.00 kS/s"); + sampleRateOptions.add("10.0 kS/s"); + sampleRateOptions.add("12.5 kS/s"); + sampleRateOptions.add("15.0 kS/s"); + sampleRateOptions.add("20.0 kS/s"); + sampleRateOptions.add("25.0 kS/s"); + sampleRateOptions.add("30.0 kS/s"); + + + rateSelection = new ComboBox("Sample Rate"); + rateSelection->addItemList(sampleRateOptions, 1); + rateSelection->setSelectedId(17, dontSendNotification); + rateSelection->addListener(this); + + rateSelection->setBounds(0,15,300,20); + addAndMakeVisible(rateSelection); + + +} + +SampleRateInterface::~SampleRateInterface() +{ + +} + +void SampleRateInterface::comboBoxChanged(ComboBox* cb) +{ + if (!(editor->acquisitionIsActive) && board->foundInputSource()) + { + if (cb == rateSelection) + { + board->setSampleRate(cb->getSelectedId()-1); + + std::cout << "Setting sample rate to index " << cb->getSelectedId()-1 << "\n"; + + CoreServices::updateSignalChain(editor); + } + } +} + +int SampleRateInterface::getSelectedId() +{ + return rateSelection->getSelectedId(); +} + +void SampleRateInterface::setSelectedId(int id) +{ + rateSelection->setSelectedId(id); +} + + +void SampleRateInterface::paint(Graphics& g) +{ + + g.setColour(Colours::darkgrey); + + g.setFont(Font("Small Text",10,Font::plain)); + + g.drawText(name, 0, 0, 200, 15, Justification::left, false); + +} + + +// Headstage Options -------------------------------------------------------------------- + +HeadstageOptionsInterface::HeadstageOptionsInterface(RHD2000Thread* board_, + RHD2000Editor* editor_, + int hsNum) : + isEnabled(false), board(board_), editor(editor_) +{ + + switch (hsNum) + { + case 0 : + name = "A"; + break; + case 1: + name = "B"; + break; + case 2: + name = "C"; + break; + case 3: + name = "D"; + break; + default: + name = "X"; + } + + hsNumber1 = hsNum*2; // data stream 1 + hsNumber2 = hsNumber1+1; // data stream 2 + + channelsOnHs1 = 0; + channelsOnHs2 = 0; + + + + hsButton1 = new UtilityButton(" ", Font("Small Text", 13, Font::plain)); + hsButton1->setRadius(3.0f); + hsButton1->setBounds(23,1,20,17); + hsButton1->setEnabledState(false); + hsButton1->setCorners(true, false, true, false); + hsButton1->addListener(this); + addAndMakeVisible(hsButton1); + + hsButton2 = new UtilityButton(" ", Font("Small Text", 13, Font::plain)); + hsButton2->setRadius(3.0f); + hsButton2->setBounds(43,1,20,17); + hsButton2->setEnabledState(false); + hsButton2->setCorners(false, true, false, true); + hsButton2->addListener(this); + addAndMakeVisible(hsButton2); + + checkEnabledState(); +} + +HeadstageOptionsInterface::~HeadstageOptionsInterface() +{ + +} + +void HeadstageOptionsInterface::checkEnabledState() +{ + isEnabled = (board->isHeadstageEnabled(hsNumber1) || + board->isHeadstageEnabled(hsNumber2)); + + if (board->isHeadstageEnabled(hsNumber1)) + { + channelsOnHs1 = board->getActiveChannelsInHeadstage(hsNumber1); + hsButton1->setLabel(String(channelsOnHs1)); + hsButton1->setEnabledState(true); + } + else + { + channelsOnHs1 = 0; + hsButton1->setLabel(" "); + hsButton1->setEnabledState(false); + } + + if (board->isHeadstageEnabled(hsNumber2)) + { + channelsOnHs2 = board->getActiveChannelsInHeadstage(hsNumber2); + hsButton2->setLabel(String(channelsOnHs2)); + hsButton2->setEnabledState(true); + } + else + { + channelsOnHs2 = 0; + hsButton2->setLabel(" "); + hsButton2->setEnabledState(false); + } + + repaint(); + +} + +void HeadstageOptionsInterface::buttonClicked(Button* button) +{ + + if (!(editor->acquisitionIsActive) && board->foundInputSource()) + { + + //std::cout << "Acquisition is not active" << "\n"; + if ((button == hsButton1) && (board->getChannelsInHeadstage(hsNumber1) == 32)) + { + if (channelsOnHs1 == 32) + channelsOnHs1 = 16; + else + channelsOnHs1 = 32; + + //std::cout << "HS1 has " << channelsOnHs1 << " channels." << "\n"; + + hsButton1->setLabel(String(channelsOnHs1)); + board->setNumChannels(hsNumber1, channelsOnHs1); + + //board->updateChannels(); + editor->updateSettings(); + + } + else if ((button == hsButton2) && (board->getChannelsInHeadstage(hsNumber2) == 32)) + { + if (channelsOnHs2 == 32) + channelsOnHs2 = 16; + else + channelsOnHs2 = 32; + + hsButton2->setLabel(String(channelsOnHs2)); + board->setNumChannels(hsNumber2, channelsOnHs2); + //board->updateChannels(); + editor->updateSettings(); + } + + CoreServices::updateSignalChain(editor); + } + +} + + +void HeadstageOptionsInterface::paint(Graphics& g) +{ + g.setColour(Colours::lightgrey); + + g.fillRoundedRectangle(5,0,getWidth()-10,getHeight(),4.0f); + + if (isEnabled) + g.setColour(Colours::black); + else + g.setColour(Colours::grey); + + g.setFont(Font("Small Text",15,Font::plain)); + + g.drawText(name, 8, 2, 200, 15, Justification::left, false); + +} + + +// (Direct OpalKelly) Audio Options -------------------------------------------------------------------- + +AudioInterface::AudioInterface(RHD2000Thread* board_, + RHD2000Editor* editor_) : + board(board_), editor(editor_) +{ + + name = "Noise Slicer"; + + lastNoiseSlicerString = "0"; + + actualNoiseSlicerLevel = 0.0f; + + noiseSlicerLevelSelection = new Label("Noise Slicer",lastNoiseSlicerString); // this is currently set in RHD2000Thread, the cleaner would be to set it here again + noiseSlicerLevelSelection->setEditable(true,false,false); + noiseSlicerLevelSelection->addListener(this); + noiseSlicerLevelSelection->setBounds(30,10,30,20); + noiseSlicerLevelSelection->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(noiseSlicerLevelSelection); + + +} + +AudioInterface::~AudioInterface() +{ + +} + + +void AudioInterface::labelTextChanged(Label* label) +{ + if (board->foundInputSource()) + { + if (label == noiseSlicerLevelSelection) + { + + Value val = label->getTextValue(); + int requestedValue = int(val.getValue()); // Note that it might be nice to translate to actual uV levels (16*value) + + if (requestedValue < 0 || requestedValue > 127) + { + CoreServices::sendStatusMessage("Value out of range."); + + label->setText(lastNoiseSlicerString, dontSendNotification); + + return; + } + + actualNoiseSlicerLevel = board->setNoiseSlicerLevel(requestedValue); + + std::cout << "Setting Noise Slicer Level to " << requestedValue << "\n"; + label->setText(String((roundFloatToInt)(actualNoiseSlicerLevel)), dontSendNotification); + + } + } + else + { + Value val = label->getTextValue(); + int requestedValue = int(val.getValue()); // Note that it might be nice to translate to actual uV levels (16*value) + if (requestedValue < 0 || requestedValue > 127) + { + CoreServices::sendStatusMessage("Value out of range."); + label->setText(lastNoiseSlicerString, dontSendNotification); + return; + } + } +} + +void AudioInterface::setNoiseSlicerLevel(int value) +{ + actualNoiseSlicerLevel = board->setNoiseSlicerLevel(value); + noiseSlicerLevelSelection->setText(String(roundFloatToInt(actualNoiseSlicerLevel)), dontSendNotification); +} + +int AudioInterface::getNoiseSlicerLevel() +{ + return actualNoiseSlicerLevel; +} + + +void AudioInterface::paint(Graphics& g) +{ + + g.setColour(Colours::darkgrey); + g.setFont(Font("Small Text",9,Font::plain)); + g.drawText(name, 0, 0, 200, 15, Justification::left, false); + g.drawText("Level: ", 0, 10, 200, 20, Justification::left, false); +} + + +// Clock Divider options +ClockDivideInterface::ClockDivideInterface(RHD2000Thread* board_, + RHD2000Editor* editor_) : + name("Clock Divider") + , lastDivideRatioString("1") + , board(board_) + , editor(editor_) + , actualDivideRatio(1) + +{ + divideRatioSelection = new Label("Clock Divide", lastDivideRatioString); + divideRatioSelection->setEditable(true,false,false); + divideRatioSelection->addListener(this); + divideRatioSelection->setBounds(30,10,30,20); + divideRatioSelection->setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(divideRatioSelection); +} + +void ClockDivideInterface::labelTextChanged(Label* label) +{ + if (board->foundInputSource()) + { + if (label == divideRatioSelection) + { + Value val = label->getTextValue(); + int requestedValue = int(val.getValue()); + + if (requestedValue < 1 || requestedValue > 65534) + { + CoreServices::sendStatusMessage("Value must be between 1 and 65534."); + label->setText(lastDivideRatioString, dontSendNotification); + return; + } + + actualDivideRatio = board->setClockDivider(requestedValue); + lastDivideRatioString = String(actualDivideRatio); + + std::cout << "Setting clock divide ratio to " << actualDivideRatio << "\n"; + label->setText(lastDivideRatioString, dontSendNotification); + } + } +} + +void ClockDivideInterface::setClockDivideRatio(int value) +{ + actualDivideRatio = board->setClockDivider(value); + divideRatioSelection->setText(String(actualDivideRatio), dontSendNotification); +} + +void ClockDivideInterface::paint(Graphics& g) +{ + + g.setColour(Colours::darkgrey); + g.setFont(Font("Small Text",9,Font::plain)); + g.drawText(name, 0, 0, 200, 15, Justification::left, false); + g.drawText("Ratio: ", 0, 10, 200, 20, Justification::left, false); +} + +// DSP Options -------------------------------------------------------------------- + +DSPInterface::DSPInterface(RHD2000Thread* board_, + RHD2000Editor* editor_) : + board(board_), editor(editor_) +{ + name = "DSP"; + + dspOffsetSelection = new Label("DspOffsetSelection",String(round(board->getDspCutoffFreq()*10.f)/10.f)); + dspOffsetSelection->setEditable(true,false,false); + dspOffsetSelection->addListener(this); + dspOffsetSelection->setBounds(0,0,30,20); + dspOffsetSelection->setColour(Label::textColourId, Colours::darkgrey); + + addAndMakeVisible(dspOffsetSelection); + +} + +DSPInterface::~DSPInterface() +{ + +} + + +void DSPInterface::labelTextChanged(Label* label) +{ + + if (!(editor->acquisitionIsActive) && board->foundInputSource()) + { + if (label == dspOffsetSelection) + { + + Value val = label->getTextValue(); + double requestedValue = double(val.getValue()); + + actualDspCutoffFreq = board->setDspCutoffFreq(requestedValue); + + std::cout << "Setting DSP Cutoff Freq to " << requestedValue << "\n"; + std::cout << "Actual DSP Cutoff Freq: " << actualDspCutoffFreq << "\n"; + label->setText(String(round(actualDspCutoffFreq*10.f)/10.f), dontSendNotification); + + } + } + else if (editor->acquisitionIsActive) + { + CoreServices::sendStatusMessage("Can't change DSP cutoff while acquisition is active!"); + } + +} + +void DSPInterface::setDspCutoffFreq(double value) +{ + actualDspCutoffFreq = board->setDspCutoffFreq(value); + dspOffsetSelection->setText(String(round(actualDspCutoffFreq*10.f)/10.f), dontSendNotification); +} + + +double DSPInterface::getDspCutoffFreq() +{ + return actualDspCutoffFreq; +} + +void DSPInterface::paint(Graphics& g) +{ + + g.setColour(Colours::darkgrey); + + g.setFont(Font("Small Text",10,Font::plain)); + +} diff --git a/Source/Plugins/PCIeRhythm/RHD2000Editor.h b/Source/Plugins/PCIeRhythm/RHD2000Editor.h new file mode 100644 index 000000000..9f4ef4f5d --- /dev/null +++ b/Source/Plugins/PCIeRhythm/RHD2000Editor.h @@ -0,0 +1,399 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef __RHD2000EDITOR_H_2AD3C591__ +#define __RHD2000EDITOR_H_2AD3C591__ + +#include <VisualizerEditorHeaders.h> + +class UtilityButton; +/** + +User interface for the RHD2000 source module. + +@see SourceNode + +*/ +class SourceNode; + +namespace PCIeRhythm { + class HeadstageOptionsInterface; + class SampleRateInterface; + class BandwidthInterface; + class DSPInterface; + class AudioInterface; + class ClockDivideInterface; + class RHD2000Thread; + struct ImpedanceData; + + + + class FPGAchannelComponent; + class RHD2000Editor; + class FPGAcanvas; + + class FPGAchannelList : public Component, + Button::Listener, ComboBox::Listener + { + public: + + FPGAchannelList(GenericProcessor* proc, Viewport* p, FPGAcanvas* c); + ~FPGAchannelList(); + void setNewName(int channelIndex, String newName); + void setNewGain(int channel, float gain); + void disableAll(); + void enableAll(); + void paint(Graphics& g); + void buttonClicked(Button* btn); + void update(); + void updateButtons(); + int getNumChannels(); + void comboBoxChanged(ComboBox* b); + void updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase); + SourceNode* proc; + + private: + Array<float> gains; + Array<ChannelType> types; + + bool chainUpdate; + + Viewport* viewport; + FPGAcanvas* canvas; + ScopedPointer<UtilityButton> impedanceButton; + ScopedPointer<ToggleButton> saveImpedanceButton; + ScopedPointer<ToggleButton> autoMeasureButton; + ScopedPointer<ComboBox> numberingScheme; + ScopedPointer<Label> numberingSchemeLabel; + OwnedArray<Label> staticLabels; + OwnedArray<FPGAchannelComponent> channelComponents; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FPGAchannelList); + }; + + + class FPGAchannelComponent : public Component, Button::Listener, public ComboBox::Listener, public Label::Listener + { + public: + FPGAchannelComponent(FPGAchannelList* cl, int ch, int gainIndex_, String name_, Array<float> gains_, ChannelType type_); + ~FPGAchannelComponent(); + Colour getDefaultColor(int ID); + void setImpedanceValues(float mag, float phase); + void disableEdit(); + void enableEdit(); + + + void setEnabledState(bool); + bool getEnabledState() + { + return isEnabled; + } + void buttonClicked(Button* btn); + void setUserDefinedData(int d); + int getUserDefinedData(); + void comboBoxChanged(ComboBox* comboBox); + void labelTextChanged(Label* lbl); + + void resized(); + + const ChannelType type; + private: + Array<float> gains; + FPGAchannelList* channelList; + ScopedPointer<Label> staticLabel, editName, impedance; + ScopedPointer<ComboBox> gainComboBox; + int channel; + String name; + int gainIndex; + int userDefinedData; + Font font; + bool isEnabled; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FPGAchannelComponent); + }; + + + class FPGAcanvas : public Visualizer, public Button::Listener + { + public: + FPGAcanvas(GenericProcessor* n); + ~FPGAcanvas(); + + void paint(Graphics& g); + + void refresh(); + + void beginAnimation(); + void endAnimation(); + + void refreshState(); + void update(); + + void setParameter(int, float); + void setParameter(int, int, int, float); + + void updateImpedance(Array<int> streams, Array<int> channels, Array<float> magnitude, Array<float> phase); + + void resized(); + void buttonClicked(Button* button); + ScopedPointer<Viewport> channelsViewport; + SourceNode* processor; + ScopedPointer<FPGAchannelList> channelList; + }; + + class RHD2000Editor : public VisualizerEditor, public ComboBox::Listener, public AsyncUpdater + + { + public: + RHD2000Editor(GenericProcessor* parentNode, RHD2000Thread*, bool useDefaultParameterEditors); + ~RHD2000Editor(); + + void buttonEvent(Button* button); + + void scanPorts(); + void comboBoxChanged(ComboBox* comboBox); + + void startAcquisition(); + void stopAcquisition(); + + void channelChanged(int channel, bool newState) override; + + void saveCustomParameters(XmlElement* xml); + void loadCustomParameters(XmlElement* xml); + Visualizer* createNewCanvas(void); + void measureImpedance(); + + void setSaveImpedance(bool en); + void setAutoMeasureImpedance(bool en); + bool getSaveImpedance(); + bool getAutoMeasureImpedance(); + + void handleAsyncUpdate(); + + private: + + OwnedArray<HeadstageOptionsInterface> headstageOptionsInterfaces; + OwnedArray<ElectrodeButton> electrodeButtons; + + ScopedPointer<SampleRateInterface> sampleRateInterface; + ScopedPointer<BandwidthInterface> bandwidthInterface; + ScopedPointer<DSPInterface> dspInterface; + + ScopedPointer<AudioInterface> audioInterface; + ScopedPointer<ClockDivideInterface> clockInterface; + + ScopedPointer<UtilityButton> rescanButton, dacTTLButton; + ScopedPointer<UtilityButton> adcButton; + ScopedPointer<UtilityButton> ledButton; + + ScopedPointer<UtilityButton> dspoffsetButton; + ScopedPointer<ComboBox> ttlSettleCombo, dacHPFcombo; + + + ScopedPointer<Label> audioLabel, ttlSettleLabel, dacHPFlabel; + + bool saveImpedances, measureWhenRecording; + + RHD2000Thread* board; + FPGAcanvas* canvas; + + ScopedPointer<ImpedanceData> impedanceData; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHD2000Editor); + + }; + + + class HeadstageOptionsInterface : public Component, + public Button::Listener + { + public: + HeadstageOptionsInterface(RHD2000Thread*, RHD2000Editor*, int hsNum); + ~HeadstageOptionsInterface(); + + void paint(Graphics& g); + + void buttonClicked(Button* button); + + void checkEnabledState(); + + private: + + int hsNumber1, hsNumber2; + int channelsOnHs1, channelsOnHs2; + String name; + + bool isEnabled; + + RHD2000Thread* board; + RHD2000Editor* editor; + + ScopedPointer<UtilityButton> hsButton1; + ScopedPointer<UtilityButton> hsButton2; + + }; + + + class BandwidthInterface : public Component, + public Label::Listener + { + public: + BandwidthInterface(RHD2000Thread*, RHD2000Editor*); + ~BandwidthInterface(); + + void paint(Graphics& g); + void labelTextChanged(Label* te); + + void setLowerBandwidth(double value); + void setUpperBandwidth(double value); + double getLowerBandwidth(); + double getUpperBandwidth(); + + private: + + String name; + + String lastLowCutString, lastHighCutString; + + RHD2000Thread* board; + RHD2000Editor* editor; + + ScopedPointer<Label> upperBandwidthSelection; + ScopedPointer<Label> lowerBandwidthSelection; + + double actualUpperBandwidth; + double actualLowerBandwidth; + + }; + + class DSPInterface : public Component, + public Label::Listener + { + public: + DSPInterface(RHD2000Thread*, RHD2000Editor*); + ~DSPInterface(); + + void paint(Graphics& g); + void labelTextChanged(Label* te); + + void setDspCutoffFreq(double value); + double getDspCutoffFreq(); + + private: + + String name; + + RHD2000Thread* board; + RHD2000Editor* editor; + + ScopedPointer<Label> dspOffsetSelection; + + double actualDspCutoffFreq; + + }; + + + + class SampleRateInterface : public Component, + public ComboBox::Listener + { + public: + SampleRateInterface(RHD2000Thread*, RHD2000Editor*); + ~SampleRateInterface(); + + int getSelectedId(); + void setSelectedId(int); + + void paint(Graphics& g); + void comboBoxChanged(ComboBox* cb); + + private: + + int sampleRate; + String name; + + RHD2000Thread* board; + RHD2000Editor* editor; + + ScopedPointer<ComboBox> rateSelection; + StringArray sampleRateOptions; + + }; + + class AudioInterface : public Component, + public Label::Listener + { + public: + AudioInterface(RHD2000Thread*, RHD2000Editor*); + ~AudioInterface(); + + void paint(Graphics& g); + void labelTextChanged(Label* te); + + void setNoiseSlicerLevel(int value); + int getNoiseSlicerLevel(); + //void setGain(double value); + //double getGain(); + + private: + + String name; + + String lastNoiseSlicerString; + String lastGainString; + + RHD2000Thread* board; + RHD2000Editor* editor; + + ScopedPointer<Label> noiseSlicerLevelSelection; + //ScopedPointer<Label> gainSelection; + + int actualNoiseSlicerLevel; + //double actualGain; + + }; + + class ClockDivideInterface : public Component, + public Label::Listener + { + public: + ClockDivideInterface(RHD2000Thread*, RHD2000Editor*); + + void paint(Graphics& g); + void labelTextChanged(Label* te); + + void setClockDivideRatio(int value); + int getClockDivideRatio() const { return actualDivideRatio; }; + + private: + + String name; + String lastDivideRatioString; + + RHD2000Thread * board; + RHD2000Editor * editor; + + ScopedPointer<Label> divideRatioSelection; + int actualDivideRatio; + + }; +}; +#endif // __RHD2000EDITOR_H_2AD3C591__ diff --git a/Source/Plugins/PCIeRhythm/RHD2000Thread.cpp b/Source/Plugins/PCIeRhythm/RHD2000Thread.cpp new file mode 100644 index 000000000..c3b02ff8b --- /dev/null +++ b/Source/Plugins/PCIeRhythm/RHD2000Thread.cpp @@ -0,0 +1,2386 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "RHD2000Thread.h" +#include "RHD2000Editor.h" + + +#if defined(_WIN32) +#define okLIB_NAME "okFrontPanel.dll" +#define okLIB_EXTENSION "*.dll" +#elif defined(__APPLE__) +#define okLIB_NAME "libokFrontPanel.dylib" +#define okLIB_EXTENSION "*.dylib" +#elif defined(__linux__) +#define okLIB_NAME "./libokFrontPanel.so" +#define okLIB_EXTENSION "*.so" +#endif + +#define CHIP_ID_RHD2132 1 +#define CHIP_ID_RHD2216 2 +#define CHIP_ID_RHD2164 4 +#define CHIP_ID_RHD2164_B 1000 +#define REGISTER_59_MISO_A 53 +#define REGISTER_59_MISO_B 58 +#define RHD2132_16CH_OFFSET 8 + +//#define DEBUG_EMULATE_HEADSTAGES 8 +//#define DEBUG_EMULATE_64CH + +#define INIT_STEP 256 + +using namespace PCIeRhythm; + +// Allocates memory for a 3-D array of doubles. +void allocateDoubleArray3D(std::vector<std::vector<std::vector<double> > >& array3D, + int xSize, int ySize, int zSize) +{ + int i, j; + + if (xSize == 0) return; + array3D.resize(xSize); + for (i = 0; i < xSize; ++i) + { + array3D[i].resize(ySize); + for (j = 0; j < ySize; ++j) + { + array3D[i][j].resize(zSize); + } + } +} + +DataThread* RHD2000Thread::createDataThread(SourceNode *sn) +{ + return new RHD2000Thread(sn); +} + +RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn), + chipRegisters(30000.0f), + numChannels(0), + deviceFound(false), + isTransmitting(false), + dacOutputShouldChange(false), + acquireAdcChannels(false), + acquireAuxChannels(true), + fastSettleEnabled(false), + fastTTLSettleEnabled(false), + fastSettleTTLChannel(-1), + ttlMode(false), + dspEnabled(true), + desiredDspCutoffFreq(0.5f), + desiredUpperBandwidth(7500.0f), + desiredLowerBandwidth(1.0f), + boardSampleRate(30000.0f), + savedSampleRateIndex(16), + cableLengthPortA(0.914f), cableLengthPortB(0.914f), cableLengthPortC(0.914f), cableLengthPortD(0.914f), // default is 3 feet (0.914 m), + audioOutputL(-1), audioOutputR(-1) ,numberingScheme(1), + newScan(true), ledsEnabled(true) +{ + impedanceThread = new RHDImpedanceMeasure(this); + memset(auxBuffer, 0, sizeof(auxBuffer)); + memset(auxSamples, 0, sizeof(auxSamples)); + + for (int i=0; i < MAX_NUM_HEADSTAGES; i++) + headstagesArray.add(new RHDHeadstage(static_cast<rhd2000PCIe::BoardDataSource>(i))); + + evalBoard = new rhd2000PCIe; + dataBuffer = new DataBuffer(2, 10000); // start with 2 channels and automatically resize + + // Open Opal Kelly XEM6010 board. + // Returns 1 if successful, -1 if FrontPanel cannot be loaded, and -2 if XEM6010 can't be found. + +#if defined(__APPLE__) + File appBundle = File::getSpecialLocation(File::currentApplicationFile); + const String executableDirectory = appBundle.getChildFile("Contents/Resources").getFullPathName(); +#else + File executable = File::getSpecialLocation(File::currentExecutableFile); + const String executableDirectory = executable.getParentDirectory().getFullPathName(); +#endif + + std::cout << executableDirectory << std::endl; + + + String dirName = executableDirectory; + libraryFilePath = dirName; + libraryFilePath += File::separatorString; + libraryFilePath += okLIB_NAME; + + dacStream = nullptr; + dacChannels = nullptr; + dacThresholds = nullptr; + dacChannelsToUpdate = nullptr; + if (openBoard()) + { + dataBlock = new Rhd2000DataBlock(1); + // upload bitfile and restore default settings + initializeBoard(); + + // automatically find connected headstages + scanPorts(); // things would appear to run more smoothly if this were done after the editor has been created + + // probably better to do this with a thread, but a timer works for now: + // startTimer(10); // initialize the board in the background + dacStream = new int[8]; + dacChannels = new int[8]; + dacThresholds = new float[8]; + dacChannelsToUpdate = new bool[8]; + for (int k = 0; k < 8; k++) + { + dacChannelsToUpdate[k] = true; + dacStream[k] = 0; + setDACthreshold(k, 65534); + dacChannels[k] = 0; + dacThresholds[k] = 0; + } + + // evalBoard->getDacInformation(dacChannels,dacThresholds); + + // setDefaultNamingScheme(numberingScheme); + //setDefaultChannelNamesAndType(); + } +} + +GenericEditor* RHD2000Thread::createEditor(SourceNode* sn) +{ + return new RHD2000Editor(sn, this, true); +} + +void RHD2000Thread::timerCallback() +{ + + stopTimer(); + +} + +RHD2000Thread::~RHD2000Thread() +{ + + std::cout << "RHD2000 interface destroyed." << std::endl; + +/* if (deviceFound) + { + int ledArray[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + evalBoard->setLedDisplay(ledArray); + } + + if (deviceFound) + evalBoard->resetFpga();*/ + + //deleteAndZero(dataBlock); + + delete[] dacStream; + delete[] dacChannels; + delete[] dacThresholds; + delete[] dacChannelsToUpdate; + +} + +bool RHD2000Thread::usesCustomNames() +{ + return true; +} + +void RHD2000Thread::setDACthreshold(int dacOutput, float threshold) +{ + dacThresholds[dacOutput]= threshold; + dacChannelsToUpdate[dacOutput] = true; + dacOutputShouldChange = true; + + // evalBoard->setDacThresholdVoltage(dacOutput,threshold); +} + +void RHD2000Thread::setDACchannel(int dacOutput, int channel) +{ + if (channel < getNumHeadstageOutputs()) + { + int channelCount = 0; + for (int i = 0; i < enabledStreams.size(); i++) + { + if (channel < channelCount + numChannelsPerDataStream[i]) + { + dacChannels[dacOutput] = channel - channelCount; + dacStream[dacOutput] = i; + break; + } + else + { + channelCount += numChannelsPerDataStream[i]; + } + } + dacChannelsToUpdate[dacOutput] = true; + dacOutputShouldChange = true; + } +} + +Array<int> RHD2000Thread::getDACchannels() +{ + Array<int> dacChannelsArray; + //dacChannelsArray.addArray(dacChannels,8); + for (int k=0; k<8; k++) + { + dacChannelsArray.add(dacChannels[k]); + } + return dacChannelsArray; + +} +bool RHD2000Thread::openBoard() +{ + if (evalBoard->open()) + { + deviceFound = true; + } + else // board could not be opened + { + bool response = AlertWindow::showOkCancelBox(AlertWindow::NoIcon, + "Acquisition board not found.", + "An acquisition board could not be found. Please connect one now.", + "OK", "Cancel", 0, 0); + + if (response) + { + openBoard(); // call recursively + } + else + { + deviceFound = false; + } + + } + + return deviceFound; + +} + + +void RHD2000Thread::initializeBoard() +{ + + // Initialize the board + std::cout << "Initializing acquisition board." << std::endl; + evalBoard->initialize(); + // This applies the following settings: + // - sample rate to 30 kHz + // - aux command banks to zero + // - aux command lengths to zero + // - continuous run mode to 'true' + // - maxTimeStep to 2^32 - 1 + // - all cable lengths to 3 feet + // - dspSettle to 'false' + // - data source mapping as 0->PortA1, 1->PortB1, 2->PortC1, 3->PortD1, etc. + // - enables all data streams + // - clears the ttlOut + // - disables all DACs and sets gain to 0 + + setSampleRate(rhd2000PCIe::SampleRate30000Hz); + evalBoard->setCableLengthMeters(rhd2000PCIe::PortA, cableLengthPortA); + evalBoard->setCableLengthMeters(rhd2000PCIe::PortB, cableLengthPortB); + evalBoard->setCableLengthMeters(rhd2000PCIe::PortC, cableLengthPortC); + evalBoard->setCableLengthMeters(rhd2000PCIe::PortD, cableLengthPortD); + + // Select RAM Bank 0 for AuxCmd3 initially, so the ADC is calibrated. + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3, 0); + + // Since our longest command sequence is 60 commands, run the SPI interface for + // 60 samples (64 for usb3 power-of two needs) + evalBoard->setMaxTimeStep(INIT_STEP); + evalBoard->setContinuousRunMode(false); + + // Start SPI interface + evalBoard->run(); + + // Wait for the 60-sample run to complete + while (evalBoard->isRunning()) + { + ; + } + + // Read the resulting single data block from the USB interface. We don't + // need to do anything with this, since it was only used for ADC calibration + ScopedPointer<Rhd2000DataBlock> dataBlock = new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams()); + + evalBoard->readDataBlock(dataBlock, INIT_STEP); + // Now that ADC calibration has been performed, we switch to the command sequence + // that does not execute ADC calibration. + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); + + + +} + +void RHD2000Thread::scanPorts() +{ + if (!deviceFound) //Safety to avoid crashes if board not present + { + return; + } + + impedanceThread->stopThreadSafely(); + //Clear previous known streams + enabledStreams.clear(); + + // Scan SPI ports + + int delay, hs, id; + int register59Value; + //int numChannelsOnPort[4] = {0, 0, 0, 0}; + rhd2000PCIe::BoardDataSource initStreamPorts[8] = + { + rhd2000PCIe::PortA1, + rhd2000PCIe::PortA2, + rhd2000PCIe::PortB1, + rhd2000PCIe::PortB2, + rhd2000PCIe::PortC1, + rhd2000PCIe::PortC2, + rhd2000PCIe::PortD1, + rhd2000PCIe::PortD2 + }; + + /* + Rhd2000EvalBoard::BoardDataSource initStreamDdrPorts[8] = + { + Rhd2000EvalBoard::PortA1Ddr, + Rhd2000EvalBoard::PortA2Ddr, + Rhd2000EvalBoard::PortB1Ddr, + Rhd2000EvalBoard::PortB2Ddr, + Rhd2000EvalBoard::PortC1Ddr, + Rhd2000EvalBoard::PortC2Ddr, + Rhd2000EvalBoard::PortD1Ddr, + Rhd2000EvalBoard::PortD2Ddr + }; + */ + + chipId.insertMultiple(0, -1, 8); + Array<int> tmpChipId(chipId); + + setSampleRate(rhd2000PCIe::SampleRate30000Hz, true); // set to 30 kHz temporarily + + // Enable all data streams, and set sources to cover one or two chips + // on Ports A-D. + evalBoard->setDataSource(0, initStreamPorts[0]); + evalBoard->setDataSource(1, initStreamPorts[1]); + evalBoard->setDataSource(2, initStreamPorts[2]); + evalBoard->setDataSource(3, initStreamPorts[3]); + evalBoard->setDataSource(4, initStreamPorts[4]); + evalBoard->setDataSource(5, initStreamPorts[5]); + evalBoard->setDataSource(6, initStreamPorts[6]); + evalBoard->setDataSource(7, initStreamPorts[7]); + + evalBoard->enableDataStream(0, true); + evalBoard->enableDataStream(1, true); + evalBoard->enableDataStream(2, true); + evalBoard->enableDataStream(3, true); + evalBoard->enableDataStream(4, true); + evalBoard->enableDataStream(5, true); + evalBoard->enableDataStream(6, true); + evalBoard->enableDataStream(7, true); + + std::cout << "Number of enabled data streams: " << evalBoard->getNumEnabledDataStreams() << std::endl; + + + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, + rhd2000PCIe::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, + rhd2000PCIe::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, + rhd2000PCIe::AuxCmd3, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, + rhd2000PCIe::AuxCmd3, 0); + + // Since our longest command sequence is 60 commands, we run the SPI + // interface for 60 samples. (64 for usb3 power-of two needs) + evalBoard->setMaxTimeStep(INIT_STEP); + evalBoard->setContinuousRunMode(false); + + ScopedPointer<Rhd2000DataBlock> dataBlock = + new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams()); + + Array<int> sumGoodDelays; + sumGoodDelays.insertMultiple(0, 0, 8); + + Array<int> indexFirstGoodDelay; + indexFirstGoodDelay.insertMultiple(0, -1, 8); + + Array<int> indexSecondGoodDelay; + indexSecondGoodDelay.insertMultiple(0, -1, 8); + + + // Run SPI command sequence at all 16 possible FPGA MISO delay settings + // to find optimum delay for each SPI interface cable. + + std::cout << "Checking for connected amplifier chips..." << std::endl; + + for (delay = 0; delay < 16; delay++)//(delay = 0; delay < 16; ++delay) + { + evalBoard->setCableDelay(rhd2000PCIe::PortA, delay); + evalBoard->setCableDelay(rhd2000PCIe::PortB, delay); + evalBoard->setCableDelay(rhd2000PCIe::PortC, delay); + evalBoard->setCableDelay(rhd2000PCIe::PortD, delay); + + // Start SPI interface. + evalBoard->run(); + + // Wait for the 60-sample run to complete. + while (evalBoard->isRunning()) + { + ; + } + // Read the resulting single data block from the USB interface. + evalBoard->readDataBlock(dataBlock, INIT_STEP); + + // Read the Intan chip ID number from each RHD2000 chip found. + // Record delay settings that yield good communication with the chip. + for (hs = 0; hs < MAX_NUM_HEADSTAGES; ++hs)//MAX_NUM_DATA_STREAMS; ++stream) + { + // std::cout << "Stream number " << stream << ", delay = " << delay << std::endl; + + id = deviceId(dataBlock, hs, register59Value); + + if (id == CHIP_ID_RHD2132 || id == CHIP_ID_RHD2216 || + (id == CHIP_ID_RHD2164 && register59Value == REGISTER_59_MISO_A)) + { + // std::cout << "Device ID found: " << id << std::endl; + + sumGoodDelays.set(hs, sumGoodDelays[hs] + 1); + + if (indexFirstGoodDelay[hs] == -1) + { + indexFirstGoodDelay.set(hs, delay); + tmpChipId.set(hs, id); + } + else if (indexSecondGoodDelay[hs] == -1) + { + indexSecondGoodDelay.set(hs, delay); + tmpChipId.set(hs, id); + } + } + } + } + + // std::cout << "Chip IDs found: "; + // for (int i = 0; i < MAX_NUM_DATA_STREAMS; ++i) + // { + // std::cout << chipId[i] << " "; + // } + //std::cout << std::endl; + +#if DEBUG_EMULATE_HEADSTAGES > 0 + if (tmpChipId[0] > 0) + { + int chipIdx = 0; + for (int hs = 0; hs < DEBUG_EMULATE_HEADSTAGES && hs < MAX_NUM_HEADSTAGES ; ++hs) + { + if (enabledStreams.size() < MAX_NUM_DATA_STREAMS(evalBoard->isUSB3())) + { +#ifdef DEBUG_EMULATE_64CH + chipId.set(chipIdx++,CHIP_ID_RHD2164); + chipId.set(chipIdx++,CHIP_ID_RHD2164_B); + enableHeadstage(hs, true, 2, 32); +#else + chipId.set(chipIdx++,CHIP_ID_RHD2132); + enableHeadstage(hs, true, 1, 32); +#endif + } + } + for (int i = 0; i < enabledStreams.size(); i++) + { + enabledStreams.set(i,Rhd2000EvalBoard::PortA1); + } + } + +#else + // Now, disable data streams where we did not find chips present. + int chipIdx = 0; + for (hs = 0; hs < MAX_NUM_HEADSTAGES; ++hs) + { + if ((tmpChipId[hs] > 0) && (enabledStreams.size() < MAX_NUM_DATA_STREAMS)) + { + chipId.set(chipIdx++,tmpChipId[hs]); + //std::cout << "Enabling headstage on stream " << stream << std::endl; + if (tmpChipId[hs] == CHIP_ID_RHD2164) //RHD2164 + { + if (enabledStreams.size() < MAX_NUM_DATA_STREAMS - 1) + { + enableHeadstage(hs,true,2,32); + chipId.set(chipIdx++,CHIP_ID_RHD2164_B); + } + else //just one stream left + { + enableHeadstage(hs,true,1,32); + } + } + else + { + enableHeadstage(hs, true,1,tmpChipId[hs] == 1 ? 32:16); + } + } + else + { + enableHeadstage(hs, false); + } + } +#endif + updateBoardStreams(); + + + std::cout << "Number of enabled data streams: " << evalBoard->getNumEnabledDataStreams() << std::endl; + + + // Set cable delay settings that yield good communication with each + // RHD2000 chip. + Array<int> optimumDelay; + optimumDelay.insertMultiple(0,0,8); + + for (hs = 0; hs < MAX_NUM_HEADSTAGES; ++hs) + { + if (sumGoodDelays[hs] == 1 || sumGoodDelays[hs] == 2) + { + optimumDelay.set(hs,indexFirstGoodDelay[hs]); + } + else if (sumGoodDelays[hs] > 2) + { + optimumDelay.set(hs,indexSecondGoodDelay[hs]); + } + } + + evalBoard->setCableDelay(rhd2000PCIe::PortA, + max(optimumDelay[0],optimumDelay[1])); + evalBoard->setCableDelay(rhd2000PCIe::PortB, + max(optimumDelay[2],optimumDelay[3])); + evalBoard->setCableDelay(rhd2000PCIe::PortC, + max(optimumDelay[4],optimumDelay[5])); + evalBoard->setCableDelay(rhd2000PCIe::PortD, + max(optimumDelay[6],optimumDelay[7])); + + cableLengthPortA = + evalBoard->estimateCableLengthMeters(max(optimumDelay[0],optimumDelay[1])); + cableLengthPortB = + evalBoard->estimateCableLengthMeters(max(optimumDelay[2],optimumDelay[3])); + cableLengthPortC = + evalBoard->estimateCableLengthMeters(max(optimumDelay[4],optimumDelay[5])); + cableLengthPortD = + evalBoard->estimateCableLengthMeters(max(optimumDelay[6],optimumDelay[7])); + + setSampleRate(savedSampleRateIndex); // restore saved sample rate + //updateRegisters(); + newScan = true; +} + +int RHD2000Thread::deviceId(Rhd2000DataBlock* dataBlock, int stream, int& register59Value) +{ + bool intanChipPresent; + + // First, check ROM registers 32-36 to verify that they hold 'INTAN', and + // the initial chip name ROM registers 24-26 that hold 'RHD'. + // This is just used to verify that we are getting good data over the SPI + // communication channel. + intanChipPresent = ((char) dataBlock->auxiliaryData[stream][2][32] == 'I' && + (char) dataBlock->auxiliaryData[stream][2][33] == 'N' && + (char) dataBlock->auxiliaryData[stream][2][34] == 'T' && + (char) dataBlock->auxiliaryData[stream][2][35] == 'A' && + (char) dataBlock->auxiliaryData[stream][2][36] == 'N' && + (char) dataBlock->auxiliaryData[stream][2][24] == 'R' && + (char) dataBlock->auxiliaryData[stream][2][25] == 'H' && + (char) dataBlock->auxiliaryData[stream][2][26] == 'D'); + + // If the SPI communication is bad, return -1. Otherwise, return the Intan + // chip ID number stored in ROM regstier 63. + if (!intanChipPresent) + { + register59Value = -1; + return -1; + } + else + { + register59Value = dataBlock->auxiliaryData[stream][2][23]; // Register 59 + return dataBlock->auxiliaryData[stream][2][19]; // chip ID (Register 63) + } +} + + + +bool RHD2000Thread::isAcquisitionActive() +{ + return isTransmitting; +} + +void RHD2000Thread::setNumChannels(int hsNum, int numChannels) +{ + if (headstagesArray[hsNum]->getNumChannels() == 32) + { + if (numChannels < headstagesArray[hsNum]->getNumChannels()) + headstagesArray[hsNum]->setHalfChannels(true); + else + headstagesArray[hsNum]->setHalfChannels(false); + numChannelsPerDataStream.set(headstagesArray[hsNum]->getStreamIndex(0), numChannels); + } +} + +int RHD2000Thread::getHeadstageChannels(int hsNum) +{ + return headstagesArray[hsNum]->getNumChannels(); +} + + +void RHD2000Thread::getEventChannelNames(StringArray& Names) +{ + Names.clear(); + for (int k = 0; k < 8; k++) + { + Names.add("TTL"+String(k+1)); + } +} + + +/* go over the old names and tests whether this particular channel name was changed. +if so, return the old name */ + + +int RHD2000Thread::modifyChannelName(int channel, String newName) +{ + ChannelCustomInfo i = channelInfo[channel]; + i.name = newName; + i.modified = true; + channelInfo.set(channel, i); + return 0; +} + +String RHD2000Thread::getChannelName(int ch) +{ + return channelInfo[ch].name; +} + +int RHD2000Thread::modifyChannelGain(int channel, float gain) +{ + ChannelCustomInfo i = channelInfo[channel]; + i.gain = gain; + i.modified = true; + channelInfo.set(channel, i); + return 0; +} + +void RHD2000Thread::setDefaultNamingScheme(int scheme) +{ + numberingScheme = scheme; + newScan = true; //if the scheme is changed, reset all names + setDefaultChannelNames(); +} + +/* This will give default names & gains to channels, unless they were manually modified by the user + In that case, the query channelModified, will return the values that need to be put */ +void RHD2000Thread::setDefaultChannelNames() +{ + int aux_counter = 1; + int channelNumber = 1; + String oldName; + //int dummy; + //float oldGain; + StringArray stream_prefix; + stream_prefix.add("A1"); + stream_prefix.add("A2"); + stream_prefix.add("B1"); + stream_prefix.add("B2"); + stream_prefix.add("C1"); + stream_prefix.add("C2"); + stream_prefix.add("D1"); + stream_prefix.add("D2"); + + for (int i = 0; i < MAX_NUM_HEADSTAGES; i++) + { + if (headstagesArray[i]->isPlugged()) + { + for (int k = 0; k < headstagesArray[i]->getNumActiveChannels(); k++) + { + if (newScan || !channelInfo[k].modified) + { + ChannelCustomInfo in; + if (numberingScheme == 1) + in.name = "CH" + String(channelNumber); + else + in.name = "CH_" + stream_prefix[i] + "_" + String(1 + k); + in.gain = getBitVolts(sn->channels[k]); + channelInfo.set(channelNumber-1, in); + + } + channelNumber++; + } + } + } + //Aux channels + for (int i = 0; i < MAX_NUM_HEADSTAGES; i++) + { + if (headstagesArray[i]->isPlugged()) + { + for (int k = 0; k < 3; k++) + { + int chn = channelNumber - 1; + + if (newScan || !channelInfo[chn].modified) + { + ChannelCustomInfo in; + if (numberingScheme == 1) + in.name = "AUX" + String(aux_counter); + else + in.name = "AUX_" + stream_prefix[i] + "_" + String(1 + k); + in.gain = getBitVolts(sn->channels[chn]); + channelInfo.set(chn, in); + + } + channelNumber++; + aux_counter++; + } + } + } + //ADC channels + if (acquireAdcChannels) + { + for (int k = 0; k < 8; k++) + { + int chn = channelNumber - 1; + if (newScan || !channelInfo[chn].modified) + { + ChannelCustomInfo in; + in.name = "ADC" + String(k + 1); + in.gain = getAdcBitVolts(k); + channelInfo.set(chn, in); + } + channelNumber++; + } + } + newScan = false; +} + +int RHD2000Thread::getNumChannels() +{ + return getNumHeadstageOutputs() + getNumAdcOutputs() + getNumAuxOutputs(); +} + +int RHD2000Thread::getNumHeadstageOutputs() +{ + numChannels = 0; + for (int i = 0; i < MAX_NUM_HEADSTAGES; i++) + { + + if (headstagesArray[i]->isPlugged()) + { + numChannels += headstagesArray[i]->getNumActiveChannels(); + } + } + + // if (numChannels > 0) + return numChannels; + //else + // return 1; // to prevent crashing with 0 channels +} + +int RHD2000Thread::getNumAuxOutputs() +{ + int numAuxOutputs = 0; + + for (int i = 0; i < MAX_NUM_HEADSTAGES; i++) + { + if (headstagesArray[i]->isPlugged() > 0) + { + numAuxOutputs += 3; + } + } + + return numAuxOutputs; + +} + +int RHD2000Thread::getNumAdcOutputs() +{ + if (acquireAdcChannels) + { + return 8; + } + else + { + return 0; + } +} + +int RHD2000Thread::getNumEventChannels() +{ + return 16; // 8 inputs, 8 outputs +} + +float RHD2000Thread::getSampleRate() +{ + return evalBoard->getSampleRate(); +} + +float RHD2000Thread::getBitVolts(Channel* ch) +{ + if (ch->type == ADC_CHANNEL) + return getAdcBitVolts(ch->index); + else if (ch->type == AUX_CHANNEL) + return 0.0000374; + else + return 0.195f; +} + +float RHD2000Thread::getAdcBitVolts(int chan) +{ + if (chan < adcBitVolts.size()) + return adcBitVolts[chan]; + else + return 0.00015258789; +} + +double RHD2000Thread::setUpperBandwidth(double upper) +{ + impedanceThread->stopThreadSafely(); + desiredUpperBandwidth = upper; + + updateRegisters(); + + return actualUpperBandwidth; +} + + +double RHD2000Thread::setLowerBandwidth(double lower) +{ + impedanceThread->stopThreadSafely(); + desiredLowerBandwidth = lower; + + updateRegisters(); + + return actualLowerBandwidth; +} + +double RHD2000Thread::setDspCutoffFreq(double freq) +{ + impedanceThread->stopThreadSafely(); + desiredDspCutoffFreq = freq; + + updateRegisters(); + + return actualDspCutoffFreq; +} + +double RHD2000Thread::getDspCutoffFreq() +{ + + return actualDspCutoffFreq; +} + +void RHD2000Thread::setDSPOffset(bool state) +{ + impedanceThread->stopThreadSafely(); + dspEnabled = state; + updateRegisters(); +} + +void RHD2000Thread::setTTLoutputMode(bool state) +{ + ttlMode = state; + dacOutputShouldChange = true; + +} + +void RHD2000Thread::setDAChpf(float cutoff, bool enabled) +{ + dacOutputShouldChange = true; + desiredDAChpf = cutoff; + desiredDAChpfState = enabled; +} + +void RHD2000Thread::setFastTTLSettle(bool state, int channel) +{ + fastTTLSettleEnabled = state; + fastSettleTTLChannel = channel; + dacOutputShouldChange = true; +} + +int RHD2000Thread::setNoiseSlicerLevel(int level) +{ + + desiredNoiseSlicerLevel = level; + /* if (deviceFound) + evalBoard->setAudioNoiseSuppress(desiredNoiseSlicerLevel);*/ + + // Level has been checked once before this and then is checked again in setAudioNoiseSuppress. + // This may be overkill - maybe API should change so that the final function returns the value? + actualNoiseSlicerLevel = level; + + return actualNoiseSlicerLevel; +} + + +bool RHD2000Thread::foundInputSource() +{ + + return deviceFound; + +} + +bool RHD2000Thread::enableHeadstage(int hsNum, bool enabled, int nStr, int strChans) +{ + + /* evalBoard->enableDataStream(hsNum, enabled);*/ + + if (enabled) + { + headstagesArray[hsNum]->setNumStreams(nStr); + headstagesArray[hsNum]->setChannelsPerStream(strChans,enabledStreams.size()); + enabledStreams.add(headstagesArray[hsNum]->getDataStream(0)); + numChannelsPerDataStream.add(strChans); + if (nStr > 1) + { + enabledStreams.add(headstagesArray[hsNum]->getDataStream(1)); + numChannelsPerDataStream.add(strChans); + } + } + else + { + int idx = enabledStreams.indexOf(headstagesArray[hsNum]->getDataStream(0)); + if (idx >= 0) + { + enabledStreams.remove(idx); + numChannelsPerDataStream.remove(idx); + } + if (headstagesArray[hsNum]->getNumStreams() > 1) + { + idx = enabledStreams.indexOf(headstagesArray[hsNum]->getDataStream(1)); + if (idx >= 0) + { + enabledStreams.remove(idx); + numChannelsPerDataStream.remove(idx); + } + } + headstagesArray[hsNum]->setNumStreams(0); + } + + /* + std::cout << "Enabled channels: "; + + for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++) + { + std::cout << numChannelsPerDataStream[i] << " "; + }*/ + + dataBuffer->resize(getNumChannels(), 10000); + + return true; +} + +void RHD2000Thread::updateBoardStreams() +{ + for (int i=0; i < MAX_NUM_DATA_STREAMS; i++) + { + if (i < enabledStreams.size()) + { + evalBoard->enableDataStream(i,true); + evalBoard->setDataSource(i,enabledStreams[i]); + } + else + { + evalBoard->enableDataStream(i,false); + } + } +} + +bool RHD2000Thread::isHeadstageEnabled(int hsNum) +{ + + return headstagesArray[hsNum]->isPlugged(); + +} + +bool RHD2000Thread::isReady() +{ + return deviceFound && (getNumChannels() > 0); +} + +int RHD2000Thread::getActiveChannelsInHeadstage(int hsNum) +{ + return headstagesArray[hsNum]->getNumActiveChannels(); +} + +int RHD2000Thread::getChannelsInHeadstage(int hsNum) +{ + return headstagesArray[hsNum]->getNumChannels(); +} + +/*void RHD2000Thread::assignAudioOut(int dacChannel, int dataChannel) +{ + if (deviceFound) + { + if (dacChannel == 0) + { + audioOutputR = dataChannel; + dacChannels[0] = dataChannel; + } + else if (dacChannel == 1) + { + audioOutputL = dataChannel; + dacChannels[1] = dataChannel; + } + + dacOutputShouldChange = true; // set a flag and take care of setting wires + // during the updateBuffer() method + // to avoid problems + } + +}*/ + +void RHD2000Thread::enableAdcs(bool t) +{ + + acquireAdcChannels = t; + + dataBuffer->resize(getNumChannels(), 10000); + +} + + +void RHD2000Thread::setSampleRate(int sampleRateIndex, bool isTemporary) +{ + impedanceThread->stopThreadSafely(); + if (!isTemporary) + { + savedSampleRateIndex = sampleRateIndex; + } + + int numUsbBlocksToRead = 0; // placeholder - make this change the number of blocks that are read in RHD2000Thread::updateBuffer() + + rhd2000PCIe::AmplifierSampleRate sampleRate; // just for local use + + switch (sampleRateIndex) + { + case 0: + sampleRate = rhd2000PCIe::SampleRate1000Hz; + numUsbBlocksToRead = 1; + boardSampleRate = 1000.0f; + break; + case 1: + sampleRate = rhd2000PCIe::SampleRate1250Hz; + numUsbBlocksToRead = 1; + boardSampleRate = 1250.0f; + break; + case 2: + sampleRate = rhd2000PCIe::SampleRate1500Hz; + numUsbBlocksToRead = 1; + boardSampleRate = 1500.0f; + break; + case 3: + sampleRate = rhd2000PCIe::SampleRate2000Hz; + numUsbBlocksToRead = 1; + boardSampleRate = 2000.0f; + break; + case 4: + sampleRate = rhd2000PCIe::SampleRate2500Hz; + numUsbBlocksToRead = 1; + boardSampleRate = 2500.0f; + break; + case 5: + sampleRate = rhd2000PCIe::SampleRate3000Hz; + numUsbBlocksToRead = 2; + boardSampleRate = 3000.0f; + break; + case 6: + sampleRate = rhd2000PCIe::SampleRate3333Hz; + numUsbBlocksToRead = 2; + boardSampleRate = 3333.0f; + break; + case 7: + sampleRate = rhd2000PCIe::SampleRate4000Hz; + numUsbBlocksToRead = 2; + boardSampleRate = 4000.0f; + break; + case 8: + sampleRate = rhd2000PCIe::SampleRate5000Hz; + numUsbBlocksToRead = 3; + boardSampleRate = 5000.0f; + break; + case 9: + sampleRate = rhd2000PCIe::SampleRate6250Hz; + numUsbBlocksToRead = 3; + boardSampleRate = 6250.0f; + break; + case 10: + sampleRate = rhd2000PCIe::SampleRate8000Hz; + numUsbBlocksToRead = 4; + boardSampleRate = 8000.0f; + break; + case 11: + sampleRate = rhd2000PCIe::SampleRate10000Hz; + numUsbBlocksToRead = 6; + boardSampleRate = 10000.0f; + break; + case 12: + sampleRate = rhd2000PCIe::SampleRate12500Hz; + numUsbBlocksToRead = 7; + boardSampleRate = 12500.0f; + break; + case 13: + sampleRate = rhd2000PCIe::SampleRate15000Hz; + numUsbBlocksToRead = 8; + boardSampleRate = 15000.0f; + break; + case 14: + sampleRate = rhd2000PCIe::SampleRate20000Hz; + numUsbBlocksToRead = 12; + boardSampleRate = 20000.0f; + break; + case 15: + sampleRate = rhd2000PCIe::SampleRate25000Hz; + numUsbBlocksToRead = 14; + boardSampleRate = 25000.0f; + break; + case 16: + sampleRate = rhd2000PCIe::SampleRate30000Hz; + numUsbBlocksToRead = 16; + boardSampleRate = 30000.0f; + break; + default: + sampleRate = rhd2000PCIe::SampleRate10000Hz; + numUsbBlocksToRead = 6; + boardSampleRate = 10000.0f; + } + + + // Select per-channel amplifier sampling rate. + evalBoard->setSampleRate(sampleRate); + + std::cout << "Sample rate set to " << evalBoard->getSampleRate() << std::endl; + + // Now that we have set our sampling rate, we can set the MISO sampling delay + // which is dependent on the sample rate. + evalBoard->setCableLengthMeters(rhd2000PCIe::PortA, cableLengthPortA); + evalBoard->setCableLengthMeters(rhd2000PCIe::PortB, cableLengthPortB); + evalBoard->setCableLengthMeters(rhd2000PCIe::PortC, cableLengthPortC); + evalBoard->setCableLengthMeters(rhd2000PCIe::PortD, cableLengthPortD); + + updateRegisters(); + +} + +void RHD2000Thread::updateRegisters() +{ + + if (!deviceFound) //Safety to avoid crashes loading a chain with Rythm node withouth a board + { + return; + } + // Set up an RHD2000 register object using this sample rate to + // optimize MUX-related register settings. + chipRegisters.defineSampleRate(boardSampleRate); + + + int commandSequenceLength; + vector<int> commandList; + + // Create a command list for the AuxCmd1 slot. This command sequence will continuously + // update Register 3, which controls the auxiliary digital output pin on each RHD2000 chip. + // In concert with the v1.4 Rhythm FPGA code, this permits real-time control of the digital + // output pin on chips on each SPI port. + chipRegisters.setDigOutLow(); // Take auxiliary output out of HiZ mode. + commandSequenceLength = chipRegisters.createCommandListUpdateDigOut(commandList); + evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd1, 0); + evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd1, 0, commandSequenceLength - 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd1, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd1, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd1, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd1, 0); + + // // Next, we'll create a command list for the AuxCmd2 slot. This command sequence + // // will sample the temperature sensor and other auxiliary ADC inputs. + commandSequenceLength = chipRegisters.createCommandListTempSensor(commandList); + evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd2, 0); + evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd2, 0, commandSequenceLength - 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd2, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd2, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd2, 0); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd2, 0); + + // Before generating register configuration command sequences, set amplifier + // bandwidth paramters. + actualDspCutoffFreq = chipRegisters.setDspCutoffFreq(desiredDspCutoffFreq); + //std::cout << "DSP Cutoff Frequency " << actualDspCutoffFreq << std::endl; + actualLowerBandwidth = chipRegisters.setLowerBandwidth(desiredLowerBandwidth); + actualUpperBandwidth = chipRegisters.setUpperBandwidth(desiredUpperBandwidth); + chipRegisters.enableDsp(dspEnabled); + //std::cout << "DSP Offset Status " << dspEnabled << std::endl; + + // turn on aux inputs + chipRegisters.enableAux1(true); + chipRegisters.enableAux2(true); + chipRegisters.enableAux3(true); + + chipRegisters.createCommandListRegisterConfig(commandList, true); + // Upload version with ADC calibration to AuxCmd3 RAM Bank 0. + evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 0); + evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0, + commandSequenceLength - 1); + + commandSequenceLength = chipRegisters.createCommandListRegisterConfig(commandList, false); + // Upload version with no ADC calibration to AuxCmd3 RAM Bank 1. + evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 1); + evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0, + commandSequenceLength - 1); + + + chipRegisters.setFastSettle(true); + + commandSequenceLength = chipRegisters.createCommandListRegisterConfig(commandList, false); + // Upload version with fast settle enabled to AuxCmd3 RAM Bank 2. + evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 2); + evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0, + commandSequenceLength - 1); + + chipRegisters.setFastSettle(false); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); + evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3, + fastSettleEnabled ? 2 : 1); +} + +void RHD2000Thread::setCableLength(int hsNum, float length) +{ + // Set the MISO sampling delay, which is dependent on the sample rate. + + switch (hsNum) + { + case 0: + evalBoard->setCableLengthFeet(rhd2000PCIe::PortA, length); + break; + case 1: + evalBoard->setCableLengthFeet(rhd2000PCIe::PortB, length); + break; + case 2: + evalBoard->setCableLengthFeet(rhd2000PCIe::PortC, length); + break; + case 3: + evalBoard->setCableLengthFeet(rhd2000PCIe::PortD, length); + break; + default: + break; + } + +} + +bool RHD2000Thread::startAcquisition() +{ + impedanceThread->waitSafely(); + dataBlock = new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams()); + + std::cout << "Expecting " << getNumChannels() << " channels." << std::endl; + + //memset(filter_states,0,256*sizeof(double)); + + /*int ledArray[8] = {1, 1, 0, 0, 0, 0, 0, 0}; + evalBoard->setLedDisplay(ledArray); + + cout << "Number of 16-bit words in FIFO: " << evalBoard->numWordsInFifo() << endl; + cout << "Is eval board running: " << evalBoard->isRunning() << endl;*/ + + + //std::cout << "Setting max timestep." << std::endl; + //evalBoard->setMaxTimeStep(100); + + + std::cout << "Starting acquisition." << std::endl; + if (1) + { + // evalBoard->setContinuousRunMode(false); + // evalBoard->setMaxTimeStep(0); + std::cout << "Flushing FIFO." << std::endl; + evalBoard->flush(); + evalBoard->setContinuousRunMode(true); + //evalBoard->printFIFOmetrics(); + evalBoard->run(); + //evalBoard->printFIFOmetrics(); + } + + blockSize = dataBlock->calculateDataBlockSizeInWords(evalBoard->getNumEnabledDataStreams()); + std::cout << "Expecting blocksize of " << blockSize << " for " << evalBoard->getNumEnabledDataStreams() << " streams" << std::endl; + //evalBoard->printFIFOmetrics(); + startThread(); + + + isTransmitting = true; + + return true; +} + +bool RHD2000Thread::stopAcquisition() +{ + + // isTransmitting = false; + std::cout << "RHD2000 data thread stopping acquisition." << std::endl; + + if (isThreadRunning()) + { + signalThreadShouldExit(); + + } + + if (waitForThreadToExit(500)) + { + std::cout << "Thread exited." << std::endl; + } + else + { + std::cout << "Thread failed to exit, continuing anyway..." << std::endl; + } + + if (deviceFound) + { + evalBoard->setContinuousRunMode(false); + evalBoard->setMaxTimeStep(0); + std::cout << "Flushing FIFO." << std::endl; + evalBoard->flush(); + // evalBoard->setContinuousRunMode(true); + // evalBoard->run(); + + } + + dataBuffer->clear(); + + /*if (deviceFound) + { + cout << "Number of 16-bit words in FIFO: " << evalBoard->numWordsInFifo() << endl; + + // std::cout << "Stopped eval board." << std::endl; + + + int ledArray[8] = {1, 0, 0, 0, 0, 0, 0, 0}; + evalBoard->setLedDisplay(ledArray); + }*/ + + isTransmitting = false; + dacOutputShouldChange = false; + + return true; +} + +bool RHD2000Thread::updateBuffer() +{ + //int chOffset; + unsigned char* bufferPtr; + //cout << "Number of 16-bit words in FIFO: " << evalBoard->numWordsInFifo() << endl; + //cout << "Block size: " << blockSize << endl; + + //std::cout << "Current number of words: " << evalBoard->numWordsInFifo() << " for " << blockSize << std::endl; + if (true)//evalBoard->isUSB3() || evalBoard->numWordsInFifo() >= blockSize) + { + bool return_code; + + return_code = evalBoard->readRawDataBlock(&bufferPtr); + + int index = 0; + int auxIndex, chanIndex; + int numStreams = enabledStreams.size(); + int nSamps = Rhd2000DataBlock::getSamplesPerDataBlock(); + + //evalBoard->printFIFOmetrics(); + for (int samp = 0; samp < nSamps; samp++) + { + int channel = -1; + + if (!Rhd2000DataBlock::checkUsbHeader(bufferPtr, index)) + { + cerr << "Error in Rhd2000EvalBoard::readDataBlock: Incorrect header." << endl; + break; + } + + index += 8; + timestamp = Rhd2000DataBlock::convertUsbTimeStamp(bufferPtr,index); + index += 4; + auxIndex = index; + //skip the aux channels + index += numStreams * 6; + // do the neural data channels first + for (int dataStream = 0; dataStream < numStreams; dataStream++) + { + int nChans = numChannelsPerDataStream[dataStream]; + chanIndex = index + 2*dataStream; + if ((chipId[dataStream] == CHIP_ID_RHD2132) && (nChans == 16)) //RHD2132 16ch. headstage + { + chanIndex += 2 * RHD2132_16CH_OFFSET*numStreams; + } + for (int chan = 0; chan < nChans; chan++) + { + channel++; + thisSample[channel] = float(*(uint16*)(bufferPtr + chanIndex) - 32768)*0.195f; + chanIndex += 2*numStreams; + } + } + index += 64 * numStreams; + //now we can do the aux channels + auxIndex += 2*numStreams; + for (int dataStream = 0; dataStream < numStreams; dataStream++) + { + if (chipId[dataStream] != CHIP_ID_RHD2164_B) + { + int auxNum = (samp+3) % 4; + if (auxNum < 3) + { + auxSamples[dataStream][auxNum] = float(*(uint16*)(bufferPtr + auxIndex) - 32768)*0.0000374; + } + for (int chan = 0; chan < 3; chan++) + { + channel++; + if (auxNum == 3) + { + auxBuffer[channel] = auxSamples[dataStream][chan]; + } + thisSample[channel] = auxBuffer[channel]; + } + } + auxIndex += 2; + + } + index += 2 * numStreams; + if (acquireAdcChannels) + { + for (int adcChan = 0; adcChan < 8; ++adcChan) + { + + channel++; + // ADC waveform units = volts + thisSample[channel] = + //0.000050354 * float(dataBlock->boardAdcData[adcChan][samp]); + 0.00015258789 * float(*(uint16*)(bufferPtr + index)) - 5 - 0.4096; // account for +/-5V input range and DC offset + index += 2; + } + } + else + { + index += 16; + } + eventCode = *(uint16*)(bufferPtr + index); + index += 4; + dataBuffer->addToBuffer(thisSample, ×tamp, &eventCode, 1); +#if 0 + // do the neural data channels first + for (int dataStream = 0; dataStream < enabledStreams.size(); dataStream++) + { + if ((chipId[dataStream] == CHIP_ID_RHD2132) && (numChannelsPerDataStream[dataStream] == 16)) //RHD2132 16ch. headstage + chOffset = RHD2132_16CH_OFFSET; + else + chOffset = 0; + for (int chan = 0; chan < numChannelsPerDataStream[dataStream]; chan++) + { + + // std::cout << "reading sample stream " << streamNumber << " chan " << chan << " sample "<< samp << std::endl; + + channel++; + + int value = dataBlock->amplifierData[dataStream][chan+chOffset][samp]; + + thisSample[channel] = float(value-32768)*0.195f; + } + + + } + + + // then do the Intan AUX channels + for (int dataStream = 0; dataStream < enabledStreams.size(); dataStream++) + { + if (chipId[dataStream] != CHIP_ID_RHD2164_B) //Channel B of 2164 shouldn't be copied + { + if (samp % 4 == 1) // every 4th sample should have auxiliary input data + { + + // std::cout << "reading sample stream " << streamNumber << " aux ADCs " << std::endl; + + channel++; + thisSample[channel] = 0.0000374 * + float(dataBlock->auxiliaryData[dataStream][1][samp + 0] - 32768); + // constant offset keeps the values visible in the LFP Viewer + + auxBuffer[channel] = thisSample[channel]; + + channel++; + thisSample[channel] = 0.0000374 * + float(dataBlock->auxiliaryData[dataStream][1][samp + 1] - 32768); + // constant offset keeps the values visible in the LFP Viewer + + auxBuffer[channel] = thisSample[channel]; + + + channel++; + thisSample[channel] = 0.0000374 * + float(dataBlock->auxiliaryData[dataStream][1][samp + 2] - 32768); + // constant offset keeps the values visible in the LFP Viewer + + auxBuffer[channel] = thisSample[channel]; + + } + else // repeat last values from buffer + { + + //std::cout << "reading sample stream " << streamNumber << " aux ADCs " << std::endl; + + channel++; + thisSample[channel] = auxBuffer[channel]; + channel++; + thisSample[channel] = auxBuffer[channel]; + channel++; + thisSample[channel] = auxBuffer[channel]; + } + } + + } + + // finally, loop through acquisition board ADC channels if necessary + if (acquireAdcChannels) + { + for (int adcChan = 0; adcChan < 8; ++adcChan) + { + + channel++; + // ADC waveform units = volts + thisSample[channel] = + //0.000050354 * float(dataBlock->boardAdcData[adcChan][samp]); + 0.00015258789 * float(dataBlock->boardAdcData[adcChan][samp]) - 5 - 0.4096; // account for +/-5V input range and DC offset + } + } + // std::cout << channel << std::endl; + + timestamp = dataBlock->timeStamp[samp]; + //timestamp = timestamp; + eventCode = dataBlock->ttlIn[samp]; + dataBuffer->addToBuffer(thisSample, ×tamp, &eventCode, 1); +#endif + } + + } + + + if (dacOutputShouldChange) + { + std::cout << "DAC" << std::endl; + for (int k=0; k<8; k++) + { + if (dacChannelsToUpdate[k]) + { + dacChannelsToUpdate[k] = false; + if (dacChannels[k] >= 0) + { + /* evalBoard->enableDac(k, true); + evalBoard->selectDacDataStream(k, dacStream[k]); + evalBoard->selectDacDataChannel(k, dacChannels[k]); + evalBoard->setDacThreshold(k, (int)abs((dacThresholds[k]/0.195) + 32768),dacThresholds[k] >= 0); + // evalBoard->setDacThresholdVoltage(k, (int) dacThresholds[k]);*/ + } + else + { + // evalBoard->enableDac(k, false); + } + } + } + + /* evalBoard->setTtlMode(ttlMode ? 1 : 0); + evalBoard->enableExternalFastSettle(fastTTLSettleEnabled); + evalBoard->setExternalFastSettleChannel(fastSettleTTLChannel); + evalBoard->setDacHighpassFilter(desiredDAChpf); + evalBoard->enableDacHighpassFilter(desiredDAChpfState); + evalBoard->enableBoardLeds(ledsEnabled); + evalBoard->setClockDivider(clockDivideFactor);*/ + + dacOutputShouldChange = false; + } + + return true; + +} + +int RHD2000Thread::getChannelFromHeadstage(int hs, int ch) +{ + int channelCount = 0; + int hsCount = 0; + if (hs < 0 || hs >= MAX_NUM_HEADSTAGES+1) + return -1; + if (hs == MAX_NUM_HEADSTAGES) //let's consider this the ADC channels + { + if (getNumAdcOutputs() > 0) + { + return getNumHeadstageOutputs() + getNumAuxOutputs() + ch; + } + else + return -1; + } + if (headstagesArray[hs]->isPlugged()) + { + if (ch < 0) + return -1; + if (ch < headstagesArray[hs]->getNumActiveChannels()) + { + for (int i = 0; i < hs; i++) + { + channelCount += headstagesArray[i]->getNumActiveChannels(); + } + return channelCount + ch; + } + else if (ch < headstagesArray[hs]->getNumActiveChannels() + 3) + { + for (int i = 0; i < MAX_NUM_HEADSTAGES; i++) + { + if (headstagesArray[i]->isPlugged()) + { + channelCount += headstagesArray[i]->getNumActiveChannels(); + if (i < hs) + hsCount++; + } + } + return channelCount + hsCount * 3 + ch-headstagesArray[hs]->getNumActiveChannels(); + } + else + { + return -1; + } + + } + else + { + return -1; + } +} + +int RHD2000Thread::getHeadstageChannel(int& hs, int ch) +{ + int channelCount = 0; + int hsCount = 0; + + if (ch < 0) + return -1; + + for (int i = 0; i < MAX_NUM_HEADSTAGES; i++) + { + if (headstagesArray[i]->isPlugged()) + { + int chans = headstagesArray[hs]->getNumActiveChannels(); + if (ch >= channelCount && ch < channelCount + chans) + { + hs = i; + return ch - channelCount; + } + channelCount += chans; + hsCount++; + } + } + if (ch < (channelCount + hsCount * 3)) //AUX + { + hsCount = (ch - channelCount) / 3; + for (int i = 0; i < MAX_NUM_HEADSTAGES; i++) + { + if (headstagesArray[i]->isPlugged()) + { + if (hsCount == 0) + { + hs = i; + return ch - channelCount; + } + hsCount--; + channelCount++; + } + } + } + return -1; +} + +void RHD2000Thread::enableBoardLeds(bool enable) +{ +#if 0 + ledsEnabled = enable; + if (isAcquisitionActive()) + dacOutputShouldChange = true; + else + evalBoard->enableBoardLeds(enable); +#endif +} + +int RHD2000Thread::setClockDivider(int divide_ratio) +{ +#if 0 + // Divide ratio should be 1 or an even number + if (divide_ratio != 1 && divide_ratio % 2) + divide_ratio--; + + // Format the divide ratio from its true value to the + // format required by the firmware + // Ratio N + // 1 0 + // >=2 Ratio/2 + if (divide_ratio == 1) + clockDivideFactor = 0; + else + clockDivideFactor = static_cast<uint16>(divide_ratio/2); + + if (isAcquisitionActive()) + dacOutputShouldChange = true; + else + evalBoard->setClockDivider(clockDivideFactor); +#endif + return divide_ratio; + +} + +void RHD2000Thread::runImpedanceTest(ImpedanceData* data) +{ + impedanceThread->stopThreadSafely(); + impedanceThread->prepareData(data); + impedanceThread->startThread(); +} + + +RHDHeadstage::RHDHeadstage(rhd2000PCIe::BoardDataSource stream) : + dataStream(stream), numStreams(0), channelsPerStream(32), halfChannels(false) +{ + streamIndex = -1; +} + +RHDHeadstage::~RHDHeadstage() +{ +} + +void RHDHeadstage::setNumStreams(int num) +{ + numStreams = num; +} + +void RHDHeadstage::setChannelsPerStream(int nchan, int index) +{ + channelsPerStream = nchan; + streamIndex = index; +} + +int RHDHeadstage::getStreamIndex(int index) +{ + return streamIndex + index; +} + +int RHDHeadstage::getNumChannels() +{ + return channelsPerStream*numStreams; +} + +int RHDHeadstage::getNumStreams() +{ + return numStreams; +} + +void RHDHeadstage::setHalfChannels(bool half) +{ + halfChannels = half; +} + +int RHDHeadstage::getNumActiveChannels() +{ + return (int)(getNumChannels() / (halfChannels ? 2 : 1)); +} + +rhd2000PCIe::BoardDataSource RHDHeadstage::getDataStream(int index) +{ + if (index < 0 || index > 1) index = 0; + return static_cast<rhd2000PCIe::BoardDataSource>(dataStream+MAX_NUM_HEADSTAGES*index); +} + +bool RHDHeadstage::isPlugged() +{ + return (numStreams > 0); +} + +/***********************************/ +/* Below is code for impedance measurements */ + +RHDImpedanceMeasure::RHDImpedanceMeasure(RHD2000Thread* b) : Thread(""), data(nullptr), board(b) +{ + // to perform electrode impedance measurements at very low frequencies. + const int maxNumBlocks = 120; + int numStreams = 8; + allocateDoubleArray3D(amplifierPreFilter, numStreams, 32, SAMPLES_PER_DATA_BLOCK_PCIE * maxNumBlocks); +} + +RHDImpedanceMeasure::~RHDImpedanceMeasure() +{ + stopThreadSafely(); +} + +void RHDImpedanceMeasure::stopThreadSafely() +{ + if (isThreadRunning()) + { + CoreServices::sendStatusMessage("Impedance measure in progress. Stopping it."); + if (!stopThread(3000)) //wait three seconds max for it to exit gracefully + { + std::cerr << "ERROR: Impedance measurement thread did not exit. Force killed it. This might led to crashes." << std::endl; + } + } +} + +void RHDImpedanceMeasure::waitSafely() +{ + if (!waitForThreadToExit(120000)) //two minutes should be enough for completing a scan + { + CoreServices::sendStatusMessage("Impedance measurement took too much. Aborting."); + if (!stopThread(3000)) //wait three seconds max for it to exit gracefully + { + std::cerr << "ERROR: Impedance measurement thread did not exit. Force killed it. This might led to crashes." << std::endl; + } + } +} + +void RHDImpedanceMeasure::prepareData(ImpedanceData* d) +{ + data = d; +} + + +// Update electrode impedance measurement frequency, after checking that +// requested test frequency lies within acceptable ranges based on the +// amplifier bandwidth and the sampling rate. See impedancefreqdialog.cpp +// for more information. +float RHDImpedanceMeasure::updateImpedanceFrequency(float desiredImpedanceFreq, bool& impedanceFreqValid) +{ + int impedancePeriod; + double lowerBandwidthLimit, upperBandwidthLimit; + float actualImpedanceFreq; + + upperBandwidthLimit = board->actualUpperBandwidth / 1.5; + lowerBandwidthLimit = board->actualLowerBandwidth * 1.5; + if (board->dspEnabled) + { + if (board->actualDspCutoffFreq > board->actualLowerBandwidth) + { + lowerBandwidthLimit = board->actualDspCutoffFreq * 1.5; + } + } + + if (desiredImpedanceFreq > 0.0) + { + impedancePeriod = (board->boardSampleRate / desiredImpedanceFreq); + if (impedancePeriod >= 4 && impedancePeriod <= 1024 && + desiredImpedanceFreq >= lowerBandwidthLimit && + desiredImpedanceFreq <= upperBandwidthLimit) + { + actualImpedanceFreq = board->boardSampleRate / impedancePeriod; + impedanceFreqValid = true; + } + else + { + actualImpedanceFreq = 0.0; + impedanceFreqValid = false; + } + } + else + { + actualImpedanceFreq = 0.0; + impedanceFreqValid = false; + } + + return actualImpedanceFreq; +} + + +// Reads numBlocks blocks of raw USB data stored in a queue of Rhd2000DataBlock +// objects, loads this data into this SignalProcessor object, scaling the raw +// data to generate waveforms with units of volts or microvolts. +int RHDImpedanceMeasure::loadAmplifierData(queue<Rhd2000DataBlock>& dataQueue, + int numBlocks, int numDataStreams) +{ + + int block, t, channel, stream; + int indexAmp = 0; + /* + int indexAux = 0; + int indexSupply = 0; + int indexAdc = 0; + int indexDig = 0; + int numWordsWritten = 0; + + int bufferIndex; + int16 tempQint16; + uint16 tempQuint16; + int32 tempQint32; + + bool triggerFound = false; + const double AnalogTriggerThreshold = 1.65; + */ + + + for (block = 0; block < numBlocks; ++block) + { + + // Load and scale RHD2000 amplifier waveforms + // (sampled at amplifier sampling rate) + for (t = 0; t < SAMPLES_PER_DATA_BLOCK_PCIE; ++t) + { + for (channel = 0; channel < 32; ++channel) + { + for (stream = 0; stream < numDataStreams; ++stream) + { + // Amplifier waveform units = microvolts + amplifierPreFilter[stream][channel][indexAmp] = 0.195 * + (dataQueue.front().amplifierData[stream][channel][t] - 32768); + } + } + ++indexAmp; + } + // We are done with this Rhd2000DataBlock object; remove it from dataQueue + dataQueue.pop(); + } + + return 0; +} + +#define PI 3.14159265359 +#define TWO_PI 6.28318530718 +#define DEGREES_TO_RADIANS 0.0174532925199 +#define RADIANS_TO_DEGREES 57.2957795132 + +// Return the magnitude and phase (in degrees) of a selected frequency component (in Hz) +// for a selected amplifier channel on the selected USB data stream. +void RHDImpedanceMeasure::measureComplexAmplitude(std::vector<std::vector<std::vector<double>>>& measuredMagnitude, + std::vector<std::vector<std::vector<double>>>& measuredPhase, + int capIndex, int stream, int chipChannel, int numBlocks, + double sampleRate, double frequency, int numPeriods) +{ + int period = (sampleRate / frequency); + int startIndex = 0; + int endIndex = startIndex + numPeriods * period - 1; + + // Move the measurement window to the end of the waveform to ignore start-up transient. + while (endIndex < SAMPLES_PER_DATA_BLOCK_PCIE * numBlocks - period) + { + startIndex += period; + endIndex += period; + } + + double iComponent, qComponent; + + // Measure real (iComponent) and imaginary (qComponent) amplitude of frequency component. + amplitudeOfFreqComponent(iComponent, qComponent, amplifierPreFilter[stream][chipChannel], + startIndex, endIndex, sampleRate, frequency); + // Calculate magnitude and phase from real (I) and imaginary (Q) components. + measuredMagnitude[stream][chipChannel][capIndex] = + sqrt(iComponent * iComponent + qComponent * qComponent); + measuredPhase[stream][chipChannel][capIndex] = + RADIANS_TO_DEGREES *atan2(qComponent, iComponent); +} + +// Returns the real and imaginary amplitudes of a selected frequency component in the vector +// data, between a start index and end index. +void RHDImpedanceMeasure::amplitudeOfFreqComponent(double& realComponent, double& imagComponent, + const std::vector<double>& data, int startIndex, + int endIndex, double sampleRate, double frequency) +{ + int length = endIndex - startIndex + 1; + const double k = TWO_PI * frequency / sampleRate; // precalculate for speed + + // Perform correlation with sine and cosine waveforms. + double meanI = 0.0; + double meanQ = 0.0; + for (int t = startIndex; t <= endIndex; ++t) + { + meanI += data.at(t) * cos(k * t); + meanQ += data.at(t) * -1.0 * sin(k * t); + } + meanI /= (double)length; + meanQ /= (double)length; + + realComponent = 2.0 * meanI; + imagComponent = 2.0 * meanQ; +} + + + +// Given a measured complex impedance that is the result of an electrode impedance in parallel +// with a parasitic capacitance (i.e., due to the amplifier input capacitance and other +// capacitances associated with the chip bondpads), this function factors out the effect of the +// parasitic capacitance to return the acutal electrode impedance. +void RHDImpedanceMeasure::factorOutParallelCapacitance(double& impedanceMagnitude, double& impedancePhase, + double frequency, double parasiticCapacitance) +{ + // First, convert from polar coordinates to rectangular coordinates. + double measuredR = impedanceMagnitude * cos(DEGREES_TO_RADIANS * impedancePhase); + double measuredX = impedanceMagnitude * sin(DEGREES_TO_RADIANS * impedancePhase); + + double capTerm = TWO_PI * frequency * parasiticCapacitance; + double xTerm = capTerm * (measuredR * measuredR + measuredX * measuredX); + double denominator = capTerm * xTerm + 2 * capTerm * measuredX + 1; + double trueR = measuredR / denominator; + double trueX = (measuredX + xTerm) / denominator; + + // Now, convert from rectangular coordinates back to polar coordinates. + impedanceMagnitude = sqrt(trueR * trueR + trueX * trueX); + impedancePhase = RADIANS_TO_DEGREES * atan2(trueX, trueR); +} + +// This is a purely empirical function to correct observed errors in the real component +// of measured electrode impedances at sampling rates below 15 kS/s. At low sampling rates, +// it is difficult to approximate a smooth sine wave with the on-chip voltage DAC and 10 kHz +// 2-pole lowpass filter. This function attempts to somewhat correct for this, but a better +// solution is to always run impedance measurements at 20 kS/s, where they seem to be most +// accurate. +void RHDImpedanceMeasure::empiricalResistanceCorrection(double& impedanceMagnitude, double& impedancePhase, + double boardSampleRate) +{ + // First, convert from polar coordinates to rectangular coordinates. + double impedanceR = impedanceMagnitude * cos(DEGREES_TO_RADIANS * impedancePhase); + double impedanceX = impedanceMagnitude * sin(DEGREES_TO_RADIANS * impedancePhase); + + // Emprically derived correction factor (i.e., no physical basis for this equation). + impedanceR /= 10.0 * exp(-boardSampleRate / 2500.0) * cos(TWO_PI * boardSampleRate / 15000.0) + 1.0; + + // Now, convert from rectangular coordinates back to polar coordinates. + impedanceMagnitude = sqrt(impedanceR * impedanceR + impedanceX * impedanceX); + impedancePhase = RADIANS_TO_DEGREES * atan2(impedanceX, impedanceR); +} + +void RHDImpedanceMeasure::run() +{ + RHD2000Editor* ed; + ed = (RHD2000Editor*)board->sn->editor.get(); + if (data == nullptr) + return; + runImpedanceMeasurement(); + restoreFPGA(); + ed->triggerAsyncUpdate(); + data = nullptr; +} + +#define CHECK_EXIT if (threadShouldExit()) return + +void RHDImpedanceMeasure::runImpedanceMeasurement() +{ + int commandSequenceLength, stream, channel, capRange; + double cSeries; + vector<int> commandList; + //int triggerIndex; // dummy reference variable; not used + queue<Rhd2000DataBlock> bufferQueue; // dummy reference variable; not used + int numdataStreams = board->evalBoard->getNumEnabledDataStreams(); + + bool rhd2164ChipPresent = false; + int chOffset; + + Array<int> enabledStreams; + for (stream = 0; stream < MAX_NUM_DATA_STREAMS; ++stream) + { + CHECK_EXIT; + if (board->evalBoard->isStreamEnabled(stream)) + { + enabledStreams.add(stream); + } + + if (board->chipId[stream] == CHIP_ID_RHD2164_B) + { + rhd2164ChipPresent = true; + } + } + + bool validImpedanceFreq; + float actualImpedanceFreq = updateImpedanceFrequency(1000.0, validImpedanceFreq); + if (!validImpedanceFreq) + { + return; + } + // Create a command list for the AuxCmd1 slot. + commandSequenceLength = board->chipRegisters.createCommandListZcheckDac(commandList, actualImpedanceFreq, 128.0); + CHECK_EXIT; + board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd1, 1); + board->evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd1, + 0, commandSequenceLength - 1); + /*if (board->fastTTLSettleEnabled) + { + board->evalBoard->enableExternalFastSettle(false); + }*/ + CHECK_EXIT; + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, + rhd2000PCIe::AuxCmd1, 1); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, + rhd2000PCIe::AuxCmd1, 1); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, + rhd2000PCIe::AuxCmd1, 1); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, + rhd2000PCIe::AuxCmd1, 1); + + // Select number of periods to measure impedance over + int numPeriods = (0.020 * actualImpedanceFreq); // Test each channel for at least 20 msec... + if (numPeriods < 5) numPeriods = 5; // ...but always measure across no fewer than 5 complete periods + double period = board->boardSampleRate / actualImpedanceFreq; + int numBlocks = ceil((numPeriods + 2.0) * period / 60.0); // + 2 periods to give time to settle initially + if (numBlocks < 2) numBlocks = 2; // need first block for command to switch channels to take effect. + + CHECK_EXIT; + board->actualDspCutoffFreq = board->chipRegisters.setDspCutoffFreq(board->desiredDspCutoffFreq); + board->actualLowerBandwidth = board->chipRegisters.setLowerBandwidth(board->desiredLowerBandwidth); + board->actualUpperBandwidth = board->chipRegisters.setUpperBandwidth(board->desiredUpperBandwidth); + board->chipRegisters.enableDsp(board->dspEnabled); + board->chipRegisters.enableZcheck(true); + commandSequenceLength = board->chipRegisters.createCommandListRegisterConfig(commandList, false); + CHECK_EXIT; + // Upload version with no ADC calibration to AuxCmd3 RAM Bank 1. + board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 3); + board->evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd3, 0, commandSequenceLength - 1); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3, 3); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3, 3); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3, 3); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3, 3); + + CHECK_EXIT; + board->evalBoard->setContinuousRunMode(false); + board->evalBoard->setMaxTimeStep(SAMPLES_PER_DATA_BLOCK_PCIE * numBlocks); + + // Create matrices of doubles of size (numStreams x 32 x 3) to store complex amplitudes + // of all amplifier channels (32 on each data stream) at three different Cseries values. + std::vector<std::vector<std::vector<double>>> measuredMagnitude; + std::vector<std::vector<std::vector<double>>> measuredPhase; + + measuredMagnitude.resize(board->evalBoard->getNumEnabledDataStreams()); + measuredPhase.resize(board->evalBoard->getNumEnabledDataStreams()); + for (int i = 0; i < board->evalBoard->getNumEnabledDataStreams(); ++i) + { + measuredMagnitude[i].resize(32); + measuredPhase[i].resize(32); + for (int j = 0; j < 32; ++j) + { + measuredMagnitude[i][j].resize(3); + measuredPhase[i][j].resize(3); + } + } + + + + double distance, minDistance, current, Cseries; + double impedanceMagnitude, impedancePhase; + + const double bestAmplitude = 250.0; // we favor voltage readings that are closest to 250 uV: not too large, + // and not too small. + const double dacVoltageAmplitude = 128 * (1.225 / 256); // this assumes the DAC amplitude was set to 128 + const double parasiticCapacitance = 14.0e-12; // 14 pF: an estimate of on-chip parasitic capacitance, + // including 10 pF of amplifier input capacitance. + double relativeFreq = actualImpedanceFreq / board->boardSampleRate; + + int bestAmplitudeIndex; + + // We execute three complete electrode impedance measurements: one each with + // Cseries set to 0.1 pF, 1 pF, and 10 pF. Then we select the best measurement + // for each channel so that we achieve a wide impedance measurement range. + for (capRange = 0; capRange < 3; ++capRange) + { + + switch (capRange) + { + case 0: + board->chipRegisters.setZcheckScale(Rhd2000Registers::ZcheckCs100fF); + cSeries = 0.1e-12; + cout << "setting capacitance to 0.1pF" << endl; + break; + case 1: + board->chipRegisters.setZcheckScale(Rhd2000Registers::ZcheckCs1pF); + cSeries = 1.0e-12; + cout << "setting capacitance to 1pF" << endl; + break; + case 2: + board->chipRegisters.setZcheckScale(Rhd2000Registers::ZcheckCs10pF); + cSeries = 10.0e-12; + cout << "setting capacitance to 10pF" << endl; + break; + } + + // Check all 32 channels across all active data streams. + for (channel = 0; channel < 32; ++channel) + { + CHECK_EXIT; + cout << "running impedance on channel " << channel << endl; + + board->chipRegisters.setZcheckChannel(channel); + commandSequenceLength = + board->chipRegisters.createCommandListRegisterConfig(commandList, false); + // Upload version with no ADC calibration to AuxCmd3 RAM Bank 1. + board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 3); + + board->evalBoard->run(); + while (board->evalBoard->isRunning()) + { + + } + queue<Rhd2000DataBlock> dataQueue; + board->evalBoard->readDataBlocks(numBlocks, dataQueue); + loadAmplifierData(dataQueue, numBlocks, numdataStreams); + for (stream = 0; stream < numdataStreams; ++stream) + { + if (board->chipId[stream] != CHIP_ID_RHD2164_B) + { + measureComplexAmplitude(measuredMagnitude, measuredPhase, + capRange, stream, channel, numBlocks, board->boardSampleRate, + actualImpedanceFreq, numPeriods); + } + } + + // If an RHD2164 chip is plugged in, we have to set the Zcheck select register to channels 32-63 + // and repeat the previous steps. + if (rhd2164ChipPresent) + { + CHECK_EXIT; + board->chipRegisters.setZcheckChannel(channel + 32); // address channels 32-63 + commandSequenceLength = + board->chipRegisters.createCommandListRegisterConfig(commandList, false); + // Upload version with no ADC calibration to AuxCmd3 RAM Bank 1. + board->evalBoard->uploadCommandList(commandList, rhd2000PCIe::AuxCmd3, 3); + + board->evalBoard->run(); + while (board->evalBoard->isRunning()) + { + + } + board->evalBoard->readDataBlocks(numBlocks, dataQueue); + loadAmplifierData(dataQueue, numBlocks, numdataStreams); + + for (stream = 0; stream < board->evalBoard->getNumEnabledDataStreams(); ++stream) + { + if (board->chipId[stream] == CHIP_ID_RHD2164_B) + { + measureComplexAmplitude(measuredMagnitude, measuredPhase, + capRange, stream, channel, numBlocks, board->boardSampleRate, + actualImpedanceFreq, numPeriods); + } + } + } + } + } + + data->streams.clear(); + data->channels.clear(); + data->magnitudes.clear(); + data->phases.clear(); + + for (stream = 0; stream < board->evalBoard->getNumEnabledDataStreams(); ++stream) + { + if ((board->chipId[stream] == CHIP_ID_RHD2132) && (board->numChannelsPerDataStream[stream] == 16)) + chOffset = RHD2132_16CH_OFFSET; + else + chOffset = 0; + + for (channel = 0; channel < board->numChannelsPerDataStream[stream]; ++channel) + { + if (1) + { + minDistance = 9.9e99; // ridiculously large number + for (capRange = 0; capRange < 3; ++capRange) + { + // Find the measured amplitude that is closest to bestAmplitude on a logarithmic scale + distance = abs(log(measuredMagnitude[stream][channel+chOffset][capRange] / bestAmplitude)); + if (distance < minDistance) + { + bestAmplitudeIndex = capRange; + minDistance = distance; + } + } + switch (bestAmplitudeIndex) + { + case 0: + Cseries = 0.1e-12; + break; + case 1: + Cseries = 1.0e-12; + break; + case 2: + Cseries = 10.0e-12; + break; + } + + // Calculate current amplitude produced by on-chip voltage DAC + current = TWO_PI * actualImpedanceFreq * dacVoltageAmplitude * Cseries; + + // Calculate impedance magnitude from calculated current and measured voltage. + impedanceMagnitude = 1.0e-6 * (measuredMagnitude[stream][channel + chOffset][bestAmplitudeIndex] / current) * + (18.0 * relativeFreq * relativeFreq + 1.0); + + // Calculate impedance phase, with small correction factor accounting for the + // 3-command SPI pipeline delay. + impedancePhase = measuredPhase[stream][channel + chOffset][bestAmplitudeIndex] + (360.0 * (3.0 / period)); + + // Factor out on-chip parasitic capacitance from impedance measurement. + factorOutParallelCapacitance(impedanceMagnitude, impedancePhase, actualImpedanceFreq, + parasiticCapacitance); + + // Perform empirical resistance correction to improve accuarcy at sample rates below + // 15 kS/s. + empiricalResistanceCorrection(impedanceMagnitude, impedancePhase, + board->boardSampleRate); + + data->streams.add(enabledStreams[stream]); + data->channels.add(channel + chOffset); + data->magnitudes.add(impedanceMagnitude); + data->phases.add(impedancePhase); + + if (impedanceMagnitude > 1000000) + cout << "stream " << stream << " channel " << 1 + channel << " magnitude: " << String(impedanceMagnitude / 1e6, 2) << " MOhm , phase : " << impedancePhase << endl; + else + cout << "stream " << stream << " channel " << 1 + channel << " magnitude: " << String(impedanceMagnitude / 1e3, 2) << " kOhm , phase : " << impedancePhase << endl; + + } + } + } + data->valid = true; + +} + +void RHDImpedanceMeasure::restoreFPGA() +{ + board->evalBoard->setContinuousRunMode(false); + board->evalBoard->setMaxTimeStep(0); + board->evalBoard->flush(); + + // Switch back to flatline + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd1, 0); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd1, 0); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd1, 0); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd1, 0); + board->evalBoard->selectAuxCommandLength(rhd2000PCIe::AuxCmd1, 0, 1); + + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortA, rhd2000PCIe::AuxCmd3, + board->fastSettleEnabled ? 2 : 1); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortB, rhd2000PCIe::AuxCmd3, + board->fastSettleEnabled ? 2 : 1); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortC, rhd2000PCIe::AuxCmd3, + board->fastSettleEnabled ? 2 : 1); + board->evalBoard->selectAuxCommandBank(rhd2000PCIe::PortD, rhd2000PCIe::AuxCmd3, + board->fastSettleEnabled ? 2 : 1); + + /*if (board->fastTTLSettleEnabled) + { + board->evalBoard->enableExternalFastSettle(true); + }*/ +} diff --git a/Source/Plugins/PCIeRhythm/RHD2000Thread.h b/Source/Plugins/PCIeRhythm/RHD2000Thread.h new file mode 100644 index 000000000..66fe3a216 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/RHD2000Thread.h @@ -0,0 +1,274 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef __RHD2000THREAD_H_2C4CBD67__ +#define __RHD2000THREAD_H_2C4CBD67__ + +#include <DataThreadHeaders.h> +#include <stdio.h> +#include <string.h> + +#include "rhythm-api/rhd2000PCIe.h" +#include "rhythm-api/rhd2000registers.h" +#include "rhythm-api/rhd2000datablock.h" + + +#define MAX_NUM_DATA_STREAMS_PCIE 16 +#define MAX_NUM_HEADSTAGES 8 + +#define MAX_NUM_CHANNELS MAX_NUM_DATA_STREAMS_PCIE*35 + +class SourceNode; + +namespace PCIeRhythm { + class RHDHeadstage; + class RHDImpedanceMeasure; + + struct ImpedanceData + { + Array<int> streams; + Array<int> channels; + Array<float> magnitudes; + Array<float> phases; + bool valid; + }; + + /** + + Communicates with the RHD2000 Evaluation Board from Intan Technologies + + @see DataThread, SourceNode + + */ + + class RHD2000Thread : public DataThread, public Timer + { + friend class RHDImpedanceMeasure; + public: + RHD2000Thread(SourceNode* sn); + ~RHD2000Thread(); + + // for communication with SourceNode processors: + bool foundInputSource(); + int getNumChannels(); + int getNumHeadstageOutputs(); + int getNumAuxOutputs(); + int getNumAdcOutputs(); + float getSampleRate(); + float getBitVolts(Channel* chan); + float getAdcBitVolts(int channelNum); + + bool isHeadstageEnabled(int hsNum); + int getChannelsInHeadstage(int hsNum); + + void setSampleRate(int index, bool temporary = false); + + double setUpperBandwidth(double upper); // set desired BW, returns actual BW + double setLowerBandwidth(double lower); + + double setDspCutoffFreq(double freq); + double getDspCutoffFreq(); + + void setDSPOffset(bool state); + + int setNoiseSlicerLevel(int level); + void setFastTTLSettle(bool state, int channel); + void setTTLoutputMode(bool state); + void setDAChpf(float cutoff, bool enabled); + + void scanPorts(); + int getNumEventChannels(); + + void enableAdcs(bool); + + bool isAcquisitionActive(); + bool isReady(); + + int modifyChannelGain(int channel, float gain); + int modifyChannelName(int channel, String newName); + void getEventChannelNames(StringArray& Names); + Array<int> getDACchannels(); + void setDACchannel(int dacOutput, int channel); + void setDACthreshold(int dacOutput, float threshold); + void setDefaultNamingScheme(int scheme); + + String getChannelName(int ch); + void setNumChannels(int hsNum, int nChannels); + int getHeadstageChannels(int hsNum); + int getActiveChannelsInHeadstage(int hsNum); + bool usesCustomNames(); + + /* Gets the absolute channel index from the headstage channel index*/ + int getChannelFromHeadstage(int hs, int ch); + /*Gets the headstage relative channel index from the absolute channel index*/ + int getHeadstageChannel(int& hs, int ch); + + void runImpedanceTest(ImpedanceData* data); + void enableBoardLeds(bool enable); + int setClockDivider(int divide_ratio); + GenericEditor* createEditor(SourceNode* sn); + + static DataThread* createDataThread(SourceNode* sn); + + private: + + bool enableHeadstage(int hsNum, bool enabled, int nStr = 1, int strChans = 32); + void updateBoardStreams(); + void setCableLength(int hsNum, float length); + + void setDefaultChannelNames(); + + ScopedPointer<rhd2000PCIe> evalBoard; + Rhd2000Registers chipRegisters; + ScopedPointer<Rhd2000DataBlock> dataBlock; + + int numChannels; + bool deviceFound; + + float thisSample[MAX_NUM_CHANNELS]; + float auxBuffer[MAX_NUM_CHANNELS]; // aux inputs are only sampled every 4th sample, so use this to buffer the samples so they can be handles just like the regular neural channels later + float auxSamples[MAX_NUM_DATA_STREAMS_PCIE][3]; + + unsigned int blockSize; + + bool isTransmitting; + + bool dacOutputShouldChange; + bool acquireAdcChannels; + bool acquireAuxChannels; + + bool fastSettleEnabled; + bool fastTTLSettleEnabled; + bool fastSettleTTLChannel; + bool ttlMode; + bool desiredDAChpfState; + double desiredDAChpf; + + bool dspEnabled; + double actualDspCutoffFreq, desiredDspCutoffFreq; + double actualUpperBandwidth, desiredUpperBandwidth; + double actualLowerBandwidth, desiredLowerBandwidth; + int actualNoiseSlicerLevel, desiredNoiseSlicerLevel; + double boardSampleRate; + int savedSampleRateIndex; + + String libraryFilePath; + + void timerCallback(); + + bool startAcquisition(); + bool stopAcquisition(); + + bool openBoard(); + void initializeBoard(); + + void updateRegisters(); + + int deviceId(Rhd2000DataBlock* dataBlock, int stream, int& register59Value); + + bool updateBuffer(); + + double cableLengthPortA, cableLengthPortB, cableLengthPortC, cableLengthPortD; + + int audioOutputL, audioOutputR; + int* dacChannels, *dacStream; + float* dacThresholds; + bool* dacChannelsToUpdate; + Array<int> chipId; + OwnedArray<RHDHeadstage> headstagesArray; + Array<rhd2000PCIe::BoardDataSource> enabledStreams; + Array<int> numChannelsPerDataStream; + + // used for data stream names... + int numberingScheme; + Array<float> adcBitVolts; + bool newScan; + ScopedPointer<RHDImpedanceMeasure> impedanceThread; + bool ledsEnabled; + + // Sync ouput divide factor + uint16 clockDivideFactor; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHD2000Thread); + }; + + class RHDHeadstage + { + public: + RHDHeadstage(rhd2000PCIe::BoardDataSource stream); + ~RHDHeadstage(); + void setNumStreams(int num); + void setChannelsPerStream(int nchan, int index); + int getStreamIndex(int index); + int getNumChannels(); + int getNumStreams(); + void setHalfChannels(bool half); //mainly used for de 16ch rhd2132 board + int getNumActiveChannels(); + rhd2000PCIe::BoardDataSource getDataStream(int index); + bool isPlugged(); + private: + rhd2000PCIe::BoardDataSource dataStream; + int streamIndex; + int numStreams; + int channelsPerStream; + bool halfChannels; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHDHeadstage); + }; + + class RHDImpedanceMeasure : public Thread + { + public: + RHDImpedanceMeasure(RHD2000Thread* b); + ~RHDImpedanceMeasure(); + void prepareData(ImpedanceData* d); + void stopThreadSafely(); + void waitSafely(); + void run(); + private: + void runImpedanceMeasurement(); + void restoreFPGA(); + void measureComplexAmplitude(std::vector<std::vector<std::vector<double>>>& measuredMagnitude, + std::vector<std::vector<std::vector<double>>>& measuredPhase, + int capIndex, int stream, int chipChannel, int numBlocks, + double sampleRate, double frequency, int numPeriods); + void amplitudeOfFreqComponent(double& realComponent, double& imagComponent, + const std::vector<double>& data, int startIndex, + int endIndex, double sampleRate, double frequency); + float updateImpedanceFrequency(float desiredImpedanceFreq, bool& impedanceFreqValid); + void factorOutParallelCapacitance(double& impedanceMagnitude, double& impedancePhase, + double frequency, double parasiticCapacitance); + void empiricalResistanceCorrection(double& impedanceMagnitude, double& impedancePhase, + double boardSampleRate); + int loadAmplifierData(queue<Rhd2000DataBlock>& dataQueue, + int numBlocks, int numDataStreams); + + std::vector<std::vector<std::vector<double>>> amplifierPreFilter; + + ImpedanceData* data; + RHD2000Thread* board; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHDImpedanceMeasure); + }; +}; +#endif // __RHD2000THREAD_H_2C4CBD67__ diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.cpp b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.cpp new file mode 100644 index 000000000..923344b62 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.cpp @@ -0,0 +1,974 @@ +#include "rhd2000PCIe.h" +#include <iostream> +#include <iomanip> +#include <fstream> +#include <queue> +#include <cmath> +#include <io.h> +#include <fcntl.h> + +#ifdef _WIN32 +#define Open _open +#define Seek _lseek +#define Read _read +#define Write _write +#define Close _close +#else +#define Open open +#define Seek lseek +#define Read read +#define Write write +#define Close close +#endif + +#include "rhd2000datablock.h" +using namespace PCIeRhythm; + +rhd2000PCIe::rhd2000PCIe() +{ + int i; + sampleRate = SampleRate30000Hz; // Rhythm FPGA boots up with 30.0 kS/s/channel sampling rate + numDataStreams = 0; + fidControl = -1; + fidStatus = -1; + fidFIFO = -1; + + for (i = 0; i < MAX_NUM_DATA_STREAMS; ++i) { + dataStreamEnabled[i] = 0; + } + + cableDelay.resize(4, -1); +} + + +rhd2000PCIe::~rhd2000PCIe() +{ + if (fidControl >= 0) + Close(fidControl); + if (fidStatus >= 0) + Close(fidStatus); + if (fidFIFO >= 0) + Close(fidFIFO); +} + +void rhd2000PCIe::writeRegister(controlAddr reg, int16_t value, int16_t mask) +{ + int regAddr = static_cast<int>(reg); + + + if (Seek(fidControl, regAddr, SEEK_SET) < 0) + { + std::cerr << "Error seeking control to addr " << regAddr << std::endl; + return; + } + + int16_t writeVal; + if (mask != 0xFFFF) + { + int16_t curVal; + int rd = Read(fidControl, &curVal, 2); + { + std::cerr << "Unsuccesful read to control addr " << regAddr << " code: " << rd << std::endl; + return; + } + if (Seek(fidControl, regAddr, SEEK_SET) < 0) + { + std::cerr << "Error re-seeking control to addr " << regAddr << std::endl; + return; + } + writeVal = (curVal & ~mask) | (value & mask); + } + else + writeVal = value; + + int wd = Write(fidControl, &writeVal, 2); + if (wd < 2) + { + std::cerr << "Unsuccesful write to control addr " << regAddr << " code: " << wd << std::endl; + return; + } +} + +int16_t rhd2000PCIe::readRegister(statusAddr reg) const +{ + int16_t value = -1; + int regAddr = static_cast<int>(reg); + if (Seek(fidStatus, regAddr, SEEK_SET) < 0) + { + std::cerr << "Error seeking status to addr " << regAddr << std::endl; + return value; + } + int rd = Read(fidStatus, &value, 2); + if (rd < 2) + { + std::cerr << "Unsuccesful read to status addr " << regAddr << " code: " << rd << std::endl; + return value; + } + return value; +} + +bool rhd2000PCIe::open() +{ + fidControl = Open("\\\\.\\xillybus_control_regs_16", O_RDWR | O_BINARY); + if (fidControl < 0) + { + std::cerr << "Error opening control file" << std::endl; + return false; + } + + fidStatus = Open("\\\\.\\xillybus_status_regs_16", O_RDONLY | O_BINARY); + if (fidStatus < 0) + { + std::cerr << "Error opening status file" << std::endl; + Close(fidControl); + fidControl = -1; + return false; + } + + fidFIFO = Open("\\\\.\\xillybus_neural_data_32", O_RDONLY | O_BINARY); + if (fidFIFO < 0) + { + std:cerr << "Error opening data FIFO" << std::endl; + Close(fidControl); + Close(fidStatus); + fidControl = -1; + fidStatus = -1; + } + std::cout << "Device files opened" << std::endl; + return true; +} + +// Initialize Rhythm FPGA to default starting values. +void rhd2000PCIe::initialize() +{ + int i; + + resetBoard(); + setSampleRate(SampleRate30000Hz); + selectAuxCommandBank(PortA, AuxCmd1, 0); + selectAuxCommandBank(PortB, AuxCmd1, 0); + selectAuxCommandBank(PortC, AuxCmd1, 0); + selectAuxCommandBank(PortD, AuxCmd1, 0); + selectAuxCommandBank(PortA, AuxCmd2, 0); + selectAuxCommandBank(PortB, AuxCmd2, 0); + selectAuxCommandBank(PortC, AuxCmd2, 0); + selectAuxCommandBank(PortD, AuxCmd2, 0); + selectAuxCommandBank(PortA, AuxCmd3, 0); + selectAuxCommandBank(PortB, AuxCmd3, 0); + selectAuxCommandBank(PortC, AuxCmd3, 0); + selectAuxCommandBank(PortD, AuxCmd3, 0); + selectAuxCommandLength(AuxCmd1, 0, 0); + selectAuxCommandLength(AuxCmd2, 0, 0); + selectAuxCommandLength(AuxCmd3, 0, 0); + setContinuousRunMode(true); + setMaxTimeStep(4294967295); // 4294967295 == (2^32 - 1) + + setCableLengthFeet(PortA, 3.0); // assume 3 ft cables + setCableLengthFeet(PortB, 3.0); + setCableLengthFeet(PortC, 3.0); + setCableLengthFeet(PortD, 3.0); + + setDspSettle(false); + + setDataSource(0, PortA1); + setDataSource(1, PortB1); + setDataSource(2, PortC1); + setDataSource(3, PortD1); + setDataSource(4, PortA2); + setDataSource(5, PortB2); + setDataSource(6, PortC2); + setDataSource(7, PortD2); + setDataSource(8, PortA1); + setDataSource(9, PortB1); + setDataSource(10, PortC1); + setDataSource(11, PortD1); + setDataSource(12, PortA2); + setDataSource(13, PortB2); + setDataSource(14, PortC2); + setDataSource(15, PortD2); + + enableDataStream(0, true); // start with only one data stream enabled + for (i = 1; i < MAX_NUM_DATA_STREAMS; i++) { + enableDataStream(i, false); + } +} + +void rhd2000PCIe::resetBoard() +{ + writeRegister(ResetRun, 0x1, 0x1); + writeRegister(ResetRun, 0x0, 0x1); +} + +// Set the per-channel sampling rate of the RHD2000 chips connected to the FPGA. +bool rhd2000PCIe::setSampleRate(AmplifierSampleRate newSampleRate) +{ + // Assuming a 100 MHz reference clock is provided to the FPGA, the programmable FPGA clock frequency + // is given by: + // + // FPGA internal clock frequency = 100 MHz * (M/D) / 2 + // + // M and D are "multiply" and "divide" integers used in the FPGA's digital clock manager (DCM) phase- + // locked loop (PLL) frequency synthesizer, and are subject to the following restrictions: + // + // M must have a value in the range of 2 - 256 + // D must have a value in the range of 1 - 256 + // M/D must fall in the range of 0.05 - 3.33 + // + // (See pages 85-86 of Xilinx document UG382 "Spartan-6 FPGA Clocking Resources" for more details.) + // + // This variable-frequency clock drives the state machine that controls all SPI communication + // with the RHD2000 chips. A complete SPI cycle (consisting of one CS pulse and 16 SCLK pulses) + // takes 80 clock cycles. The SCLK period is 4 clock cycles; the CS pulse is high for 14 clock + // cycles between commands. + // + // Rhythm samples all 32 channels and then executes 3 "auxiliary" commands that can be used to read + // and write from other registers on the chip, or to sample from the temperature sensor or auxiliary ADC + // inputs, for example. Therefore, a complete cycle that samples from each amplifier channel takes + // 80 * (32 + 3) = 80 * 35 = 2800 clock cycles. + // + // So the per-channel sampling rate of each amplifier is 2800 times slower than the clock frequency. + // + // Based on these design choices, we can use the following values of M and D to generate the following + // useful amplifier sampling rates for electrophsyiological applications: + // + // M D clkout frequency per-channel sample rate per-channel sample period + // --- --- ---------------- ----------------------- ------------------------- + // 7 125 2.80 MHz 1.00 kS/s 1000.0 usec = 1.0 msec + // 7 100 3.50 MHz 1.25 kS/s 800.0 usec + // 21 250 4.20 MHz 1.50 kS/s 666.7 usec + // 14 125 5.60 MHz 2.00 kS/s 500.0 usec + // 35 250 7.00 MHz 2.50 kS/s 400.0 usec + // 21 125 8.40 MHz 3.00 kS/s 333.3 usec + // 14 75 9.33 MHz 3.33 kS/s 300.0 usec + // 28 125 11.20 MHz 4.00 kS/s 250.0 usec + // 7 25 14.00 MHz 5.00 kS/s 200.0 usec + // 7 20 17.50 MHz 6.25 kS/s 160.0 usec + // 112 250 22.40 MHz 8.00 kS/s 125.0 usec + // 14 25 28.00 MHz 10.00 kS/s 100.0 usec + // 7 10 35.00 MHz 12.50 kS/s 80.0 usec + // 21 25 42.00 MHz 15.00 kS/s 66.7 usec + // 28 25 56.00 MHz 20.00 kS/s 50.0 usec + // 35 25 70.00 MHz 25.00 kS/s 40.0 usec + // 42 25 84.00 MHz 30.00 kS/s 33.3 usec + // + // To set a new clock frequency, assert new values for M and D (e.g., using okWireIn modules) and + // pulse DCM_prog_trigger high (e.g., using an okTriggerIn module). If this module is reset, it + // reverts to a per-channel sampling rate of 30.0 kS/s. + + unsigned long M, D; + + switch (newSampleRate) { + case SampleRate1000Hz: + M = 7; + D = 125; + break; + case SampleRate1250Hz: + M = 7; + D = 100; + break; + case SampleRate1500Hz: + M = 21; + D = 250; + break; + case SampleRate2000Hz: + M = 14; + D = 125; + break; + case SampleRate2500Hz: + M = 35; + D = 250; + break; + case SampleRate3000Hz: + M = 21; + D = 125; + break; + case SampleRate3333Hz: + M = 14; + D = 75; + break; + case SampleRate4000Hz: + M = 28; + D = 125; + break; + case SampleRate5000Hz: + M = 7; + D = 25; + break; + case SampleRate6250Hz: + M = 7; + D = 20; + break; + case SampleRate8000Hz: + M = 112; + D = 250; + break; + case SampleRate10000Hz: + M = 14; + D = 25; + break; + case SampleRate12500Hz: + M = 7; + D = 10; + break; + case SampleRate15000Hz: + M = 21; + D = 25; + break; + case SampleRate20000Hz: + M = 28; + D = 25; + break; + case SampleRate25000Hz: + M = 35; + D = 25; + break; + case SampleRate30000Hz: + M = 42; + D = 25; + break; + default: + return(false); + } + + sampleRate = newSampleRate; + + // Wait for DcmProgDone = 1 before reprogramming clock synthesizer + while (isDcmProgDone() == false) {} + + // Reprogram clock synthesizer + writeRegister(DataFreqPll, (256 * M + D)); + + // Wait for DataClkLocked = 1 before allowing data acquisition to continue + while (isDataClockLocked() == false) {} + + return(true); +} + +double rhd2000PCIe::getSampleRate() const +{ + switch (sampleRate) { + case SampleRate1000Hz: + return 1000.0; + break; + case SampleRate1250Hz: + return 1250.0; + break; + case SampleRate1500Hz: + return 1500.0; + break; + case SampleRate2000Hz: + return 2000.0; + break; + case SampleRate2500Hz: + return 2500.0; + break; + case SampleRate3000Hz: + return 3000.0; + break; + case SampleRate3333Hz: + return (10000.0 / 3.0); + break; + case SampleRate4000Hz: + return 4000.0; + break; + case SampleRate5000Hz: + return 5000.0; + break; + case SampleRate6250Hz: + return 6250.0; + break; + case SampleRate8000Hz: + return 8000.0; + break; + case SampleRate10000Hz: + return 10000.0; + break; + case SampleRate12500Hz: + return 12500.0; + break; + case SampleRate15000Hz: + return 15000.0; + break; + case SampleRate20000Hz: + return 20000.0; + break; + case SampleRate25000Hz: + return 25000.0; + break; + case SampleRate30000Hz: + return 30000.0; + break; + default: + return -1.0; + } +} + +rhd2000PCIe::AmplifierSampleRate rhd2000PCIe::getSampleRateEnum() const +{ + return sampleRate; +} + +// Upload an auxiliary command list to a particular command slot (AuxCmd1, AuxCmd2, or AuxCmd3) and RAM bank (0-15) +// on the FPGA. +void rhd2000PCIe::uploadCommandList(const vector<int> &commandList, AuxCmdSlot auxCommandSlot, int bank) +{ + unsigned int i; + const char* devFile; + int bankPos; + + if (auxCommandSlot != AuxCmd1 && auxCommandSlot != AuxCmd2 && auxCommandSlot != AuxCmd3) { + cerr << "Error in Rhd2000EvalBoard::uploadCommandList: auxCommandSlot out of range." << endl; + return; + } + + if (bank < 0 || bank > 15) { + cerr << "Error in Rhd2000EvalBoard::uploadCommandList: bank out of range." << endl; + return; + } + + switch (auxCommandSlot) + { + case AuxCmd1: + devFile = "\\\\.\\xillybus_auxcmd1_membank_16"; + break; + case AuxCmd2: + devFile = "\\\\.\\xillybus_auxcmd2_membank_16"; + break; + case AuxCmd3: + devFile = "\\\\.\\xillybus_auxcmd3_membank_16"; + break; + default: + devFile = ""; + } + bankPos = 2048 * bank; + int fidMem = Open(devFile, O_WRONLY | O_BINARY); + if (fidMem < 0) + { + std::cerr << "Error opening auxcmd device " << auxCommandSlot << std::endl; + return; + } + if (Seek(fidMem, bankPos, SEEK_SET) < 0) + { + std::cerr << "Error seeking auxcmd " << auxCommandSlot <<" to addr " << bankPos << std::endl; + Close(fidMem); + return; + } + + for (i = 0; i < commandList.size(); ++i) { + int16_t value = commandList[i]; + int wd = Write(fidControl, &value, 2); + if (wd < 2) + { + std::cerr << "Error writing auxcmd " << auxCommandSlot << " index " << i << std::endl; + } + } + Close(fidMem); +} + +// Select an auxiliary command slot (AuxCmd1, AuxCmd2, or AuxCmd3) and bank (0-15) for a particular SPI port +// (PortA, PortB, PortC, or PortD) on the FPGA. +void rhd2000PCIe::selectAuxCommandBank(BoardPort port, AuxCmdSlot auxCommandSlot, int bank) +{ + int bitShift; + + if (auxCommandSlot != AuxCmd1 && auxCommandSlot != AuxCmd2 && auxCommandSlot != AuxCmd3) { + cerr << "Error in Rhd2000EvalBoard::selectAuxCommandBank: auxCommandSlot out of range." << endl; + return; + } + if (bank < 0 || bank > 15) { + cerr << "Error in Rhd2000EvalBoard::selectAuxCommandBank: bank out of range." << endl; + return; + } + + switch (port) { + case PortA: + bitShift = 0; + break; + case PortB: + bitShift = 4; + break; + case PortC: + bitShift = 8; + break; + case PortD: + bitShift = 12; + break; + } + + switch (auxCommandSlot) { + case AuxCmd1: + writeRegister(AuxCmdBank1, bank << bitShift, 0x000f << bitShift); + break; + case AuxCmd2: + writeRegister(AuxCmdBank2, bank << bitShift, 0x000f << bitShift); + break; + case AuxCmd3: + writeRegister(AuxCmdBank3, bank << bitShift, 0x000f << bitShift); + break; + } +} + +// Specify a command sequence length (endIndex = 0-1023) and command loop index (0-1023) for a particular +// auxiliary command slot (AuxCmd1, AuxCmd2, or AuxCmd3). +void rhd2000PCIe::selectAuxCommandLength(AuxCmdSlot auxCommandSlot, int loopIndex, int endIndex) +{ + if (auxCommandSlot != AuxCmd1 && auxCommandSlot != AuxCmd2 && auxCommandSlot != AuxCmd3) { + cerr << "Error in Rhd2000EvalBoard::selectAuxCommandLength: auxCommandSlot out of range." << endl; + return; + } + + if (loopIndex < 0 || loopIndex > 1023) { + cerr << "Error in Rhd2000EvalBoard::selectAuxCommandLength: loopIndex out of range." << endl; + return; + } + + if (endIndex < 0 || endIndex > 1023) { + cerr << "Error in Rhd2000EvalBoard::selectAuxCommandLength: endIndex out of range." << endl; + return; + } + + switch (auxCommandSlot) { + case AuxCmd1: + writeRegister(AuxCmdLoop1, loopIndex); + writeRegister(AuxCmdLength1, endIndex); + break; + case AuxCmd2: + writeRegister(AuxCmdLoop2, loopIndex); + writeRegister(AuxCmdLength2, endIndex); + break; + case AuxCmd3: + writeRegister(AuxCmdLoop3, loopIndex); + writeRegister(AuxCmdLength3, endIndex); + break; + } +} + +// Set the FPGA to run continuously once started (if continuousMode == true) or to run until +// maxTimeStep is reached (if continuousMode == false). +void rhd2000PCIe::setContinuousRunMode(bool continuousMode) +{ + if (continuousMode) { + writeRegister(ResetRun, 0x02, 0x2); + } + else { + writeRegister(ResetRun, 0x00, 0x2); + } +} + +void rhd2000PCIe::setMaxTimeStep(unsigned int maxTimeStep) +{ + unsigned int maxTimeStepLsb, maxTimeStepMsb; + + maxTimeStepLsb = maxTimeStep & 0x0000ffff; + maxTimeStepMsb = maxTimeStep & 0xffff0000; + + writeRegister(MaxTimeStepLsb, maxTimeStepLsb); + writeRegister(MaxTimeStepMsb, maxTimeStepMsb >> 16); +} + +// Initiate SPI data acquisition. +void rhd2000PCIe::run() +{ + writeRegister(StartTrigger, 0x1); +} + +// Is the FPGA currently running? +bool rhd2000PCIe::isRunning() const +{ + int value; + + value = readRegister(SpiRunning); + + if ((value & 0x01) == 0) { + return false; + } + else { + return true; + } +} + +// Set the delay for sampling the MISO line on a particular SPI port (PortA - PortD), in integer clock +// steps, where each clock step is 1/2800 of a per-channel sampling period. +// Note: Cable delay must be updated after sampleRate is changed, since cable delay calculations are +// based on the clock frequency! +void rhd2000PCIe::setCableDelay(BoardPort port, int delay) +{ + int bitShift; + + if (delay < 0 || delay > 15) { + cerr << "Warning in Rhd2000EvalBoard::setCableDelay: delay out of range: " << delay << endl; + } + + if (delay < 0) delay = 0; + if (delay > 15) delay = 15; + + switch (port) { + case PortA: + bitShift = 0; + cableDelay[0] = delay; + break; + case PortB: + bitShift = 4; + cableDelay[1] = delay; + break; + case PortC: + bitShift = 8; + cableDelay[2] = delay; + break; + case PortD: + bitShift = 12; + cableDelay[3] = delay; + break; + default: + cerr << "Error in RHD2000EvalBoard::setCableDelay: unknown port." << endl; + } + + writeRegister(MisoDelay, delay << bitShift, 0x000f << bitShift); +} + +// Set the delay for sampling the MISO line on a particular SPI port (PortA - PortD) based on the length +// of the cable between the FPGA and the RHD2000 chip (in meters). +// Note: Cable delay must be updated after sampleRate is changed, since cable delay calculations are +// based on the clock frequency! +void rhd2000PCIe::setCableLengthMeters(BoardPort port, double lengthInMeters) +{ + int delay; + double tStep, cableVelocity, distance, timeDelay; + const double speedOfLight = 299792458.0; // units = meters per second + const double xilinxLvdsOutputDelay = 1.9e-9; // 1.9 ns Xilinx LVDS output pin delay + const double xilinxLvdsInputDelay = 1.4e-9; // 1.4 ns Xilinx LVDS input pin delay + const double rhd2000Delay = 9.0e-9; // 9.0 ns RHD2000 SCLK-to-MISO delay + const double misoSettleTime = 6.7e-9; // 6.7 ns delay after MISO changes, before we sample it + + tStep = 1.0 / (2800.0 * getSampleRate()); // data clock that samples MISO has a rate 35 x 80 = 2800x higher than the sampling rate + // cableVelocity = 0.67 * speedOfLight; // propogation velocity on cable: version 1.3 and earlier + cableVelocity = 0.555 * speedOfLight; // propogation velocity on cable: version 1.4 improvement based on cable measurements + distance = 2.0 * lengthInMeters; // round trip distance data must travel on cable + timeDelay = (distance / cableVelocity) + xilinxLvdsOutputDelay + rhd2000Delay + xilinxLvdsInputDelay + misoSettleTime; + + delay = (int)floor(((timeDelay / tStep) + 1.0) + 0.5); + + if (delay < 1) delay = 1; // delay of zero is too short (due to I/O delays), even for zero-length cables + + setCableDelay(port, delay); +} + +// Same function as above, but accepts lengths in feet instead of meters +void rhd2000PCIe::setCableLengthFeet(BoardPort port, double lengthInFeet) +{ + setCableLengthMeters(port, 0.3048 * lengthInFeet); // convert feet to meters +} + +// Estimate cable length based on a particular delay used in setCableDelay. +// (Note: Depends on sample rate.) +double rhd2000PCIe::estimateCableLengthMeters(int delay) const +{ + double tStep, cableVelocity, distance; + const double speedOfLight = 299792458.0; // units = meters per second + const double xilinxLvdsOutputDelay = 1.9e-9; // 1.9 ns Xilinx LVDS output pin delay + const double xilinxLvdsInputDelay = 1.4e-9; // 1.4 ns Xilinx LVDS input pin delay + const double rhd2000Delay = 9.0e-9; // 9.0 ns RHD2000 SCLK-to-MISO delay + const double misoSettleTime = 6.7e-9; // 6.7 ns delay after MISO changes, before we sample it + + tStep = 1.0 / (2800.0 * getSampleRate()); // data clock that samples MISO has a rate 35 x 80 = 2800x higher than the sampling rate + // cableVelocity = 0.67 * speedOfLight; // propogation velocity on cable: version 1.3 and earlier + cableVelocity = 0.555 * speedOfLight; // propogation velocity on cable: version 1.4 improvement based on cable measurements + + // distance = cableVelocity * (delay * tStep - (xilinxLvdsOutputDelay + rhd2000Delay + xilinxLvdsInputDelay)); // version 1.3 and earlier + distance = cableVelocity * ((((double)delay) - 1.0) * tStep - (xilinxLvdsOutputDelay + rhd2000Delay + xilinxLvdsInputDelay + misoSettleTime)); // version 1.4 improvement + if (distance < 0.0) distance = 0.0; + + return (distance / 2.0); +} + +// Same function as above, but returns length in feet instead of meters +double rhd2000PCIe::estimateCableLengthFeet(int delay) const +{ + return 3.2808 * estimateCableLengthMeters(delay); +} + +// Turn on or off DSP settle function in the FPGA. (Only executes when CONVERT commands are sent.) +void rhd2000PCIe::setDspSettle(bool enabled) +{ + writeRegister(ResetRun, (enabled ? 0x04 : 0x00), 0x04); +} + +// Assign a particular data source (e.g., PortA1, PortA2, PortB1,...) to one of the eight +// available USB data streams (0-7). +void rhd2000PCIe::setDataSource(int stream, BoardDataSource dataSource) +{ + int bitShift; + controlAddr endPoint; + + if (stream < 0 || stream >(MAX_NUM_DATA_STREAMS - 1)) { + cerr << "Error in Rhd2000EvalBoard::setDataSource: stream out of range." << endl; + return; + } + + switch (stream) { + case 0: + endPoint = DataStreamSel1234; + bitShift = 0; + break; + case 1: + endPoint = DataStreamSel1234; + bitShift = 4; + break; + case 2: + endPoint = DataStreamSel1234; + bitShift = 8; + break; + case 3: + endPoint = DataStreamSel1234; + bitShift = 12; + break; + case 4: + endPoint = DataStreamSel5678; + bitShift = 0; + break; + case 5: + endPoint = DataStreamSel5678; + bitShift = 4; + break; + case 6: + endPoint = DataStreamSel5678; + bitShift = 8; + break; + case 7: + endPoint = DataStreamSel5678; + bitShift = 12; + break; + case 8: + endPoint = DataStreamSel9ABC; + bitShift = 0; + break; + case 9: + endPoint = DataStreamSel9ABC; + bitShift = 4; + break; + case 10: + endPoint = DataStreamSel9ABC; + bitShift = 8; + break; + case 11: + endPoint = DataStreamSel9ABC; + bitShift = 12; + break; + case 12: + endPoint = DataStreamSelDEF10; + bitShift = 0; + break; + case 13: + endPoint = DataStreamSelDEF10; + bitShift = 4; + break; + case 14: + endPoint = DataStreamSelDEF10; + bitShift = 8; + break; + case 15: + endPoint = DataStreamSelDEF10; + bitShift = 12; + break; + } + + writeRegister(endPoint, dataSource << bitShift, 0x000f << bitShift); +} + +// Enable or disable one of the eight available USB data streams (0-7). +void rhd2000PCIe::enableDataStream(int stream, bool enabled) +{ + if (stream < 0 || stream >(MAX_NUM_DATA_STREAMS - 1)) { + cerr << "Error in Rhd2000EvalBoard::setDataSource: stream out of range." << endl; + return; + } + + if (enabled) { + if (dataStreamEnabled[stream] == 0) { + writeRegister(DataStreamEn, 0x0001 << stream, 0x0001 << stream); + dataStreamEnabled[stream] = 1; + ++numDataStreams; + } + } + else { + if (dataStreamEnabled[stream] == 1) { + writeRegister(DataStreamEn, 0x0000 << stream, 0x0001 << stream); + dataStreamEnabled[stream] = 0; + numDataStreams--; + } + } +} + +// Returns the number of enabled data streams. +int rhd2000PCIe::getNumEnabledDataStreams() const +{ + return numDataStreams; +} + +// Is variable-frequency clock DCM programming done? +bool rhd2000PCIe::isDcmProgDone() const +{ + int value; + + value = readRegister(DataClkLocked); + + return ((value & 0x0002) > 1); +} + +// Is variable-frequency clock PLL locked? +bool rhd2000PCIe::isDataClockLocked() const +{ + int value; + + value = readRegister(DataClkLocked); + + return ((value & 0x0001) > 0); +} + +// Return FPGA cable delay for selected SPI port. +int rhd2000PCIe::getCableDelay(BoardPort port) const +{ + switch (port) { + case PortA: + return cableDelay[0]; + case PortB: + return cableDelay[1]; + case PortC: + return cableDelay[2]; + case PortD: + return cableDelay[3]; + default: + cerr << "Error in RHD2000EvalBoard::getCableDelay: unknown port." << endl; + return -1; + } +} + +// Return FPGA cable delays for all SPI ports. +void rhd2000PCIe::getCableDelay(vector<int> &delays) const +{ + if (delays.size() != 4) { + delays.resize(4); + } + for (int i = 0; i < 4; ++i) { + delays[i] = cableDelay[i]; + } +} + +bool rhd2000PCIe::isStreamEnabled(int streamIndex) +{ + if (streamIndex < 0 || streamIndex >(MAX_NUM_DATA_STREAMS - 1)) + return false; + + return dataStreamEnabled[streamIndex]; +} + +bool rhd2000PCIe::readRawDataBlock(unsigned char** bufferPtr, int nSamples) +{ + unsigned int numBytesToRead; + + numBytesToRead = 2 * Rhd2000DataBlock::calculateDataBlockSizeInWords(numDataStreams, nSamples); + + if (numBytesToRead > DATA_BUFFER_SIZE) { + cerr << "Error in rhd2000PCIe::readRawDataBlock: Data buffer size exceeded. " << + "Increase value of DATA_BUFFER_SIZE." << endl; + *bufferPtr = nullptr; + return false; + } + + int nr = Read(fidFIFO, dataBuffer, numBytesToRead); + + if (nr < 0) + { + std::cerr << "Error reading from pipe" << std::endl; + } + else if (nr < numBytesToRead) + { + cerr << "CRITICAL: Timeout on pipe read. Check block and buffer sizes." << endl; + } + + *bufferPtr = dataBuffer; + return true; +} + +void rhd2000PCIe::flush() +{ + int nr = 0; + do + { + nr = Read(fidFIFO, &dataBuffer, DATA_BUFFER_SIZE); + + } while (nr > 0); + +} + +// Read data block from the USB interface, if one is available. Returns true if data block +// was available. +bool rhd2000PCIe::readDataBlock(Rhd2000DataBlock *dataBlock, int nSamples) +{ + unsigned int numBytesToRead; + long res; + + numBytesToRead = 2 * dataBlock->calculateDataBlockSizeInWords(numDataStreams, nSamples); + + if (numBytesToRead > DATA_BUFFER_SIZE) { + cerr << "Error in rhd2000PCIe::readDataBlock: Data buffer size exceeded. " << + "Increase value of DATA_BUFFER_SIZE." << endl; + return false; + } + + int nr = Read(fidFIFO, dataBuffer, numBytesToRead); + + if (nr < 0) + { + std::cerr << "Error reading from pipe" << std::endl; + } + else if (nr < numBytesToRead) + { + cerr << "CRITICAL: Timeout on pipe read. Check block and buffer sizes." << endl; + } + + dataBlock->fillFromUsbBuffer(dataBuffer, 0, numDataStreams, nSamples); + + return true; +} + +// Reads a certain number of USB data blocks, if the specified number is available, and appends them +// to queue. Returns true if data blocks were available. +bool rhd2000PCIe::readDataBlocks(int numBlocks, queue<Rhd2000DataBlock> &dataQueue) +{ + unsigned int numWordsToRead, numBytesToRead; + int i; + Rhd2000DataBlock *dataBlock; + + + numWordsToRead = numBlocks * dataBlock->calculateDataBlockSizeInWords(numDataStreams); + + + numBytesToRead = 2 * numWordsToRead; + + if (numBytesToRead > DATA_BUFFER_SIZE) { + cerr << "Error in rhd2000PCIe::readDataBlocks: Data buffer size exceeded. " << + "Increase value of DATA_BUFFER_SIZE." << endl; + return false; + } + + int nr = Read(fidFIFO, dataBuffer, numBytesToRead); + + if (nr < 0) + { + std::cerr << "Error reading from pipe" << std::endl; + } + else if (nr < numBytesToRead) + { + cerr << "CRITICAL: Timeout on pipe read. Check block and buffer sizes." << endl; + } + + dataBlock = new Rhd2000DataBlock(numDataStreams); + for (i = 0; i < numBlocks; ++i) { + dataBlock->fillFromUsbBuffer(dataBuffer, i, numDataStreams); + dataQueue.push(*dataBlock); + } + delete dataBlock; + + return true; +} \ No newline at end of file diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.h b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.h new file mode 100644 index 000000000..638728a71 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000PCIe.h @@ -0,0 +1,164 @@ +#ifndef RHD2000PCIE_H +#define RHD2000PCIE_H + +#include <vector> +#include <cstdint> +#include <queue> + +#define MAX_NUM_DATA_STREAMS 16 +#define DATA_BUFFER_SIZE 2560000 + +using namespace std; + +namespace PCIeRhythm { + class Rhd2000DataBlock; + + class rhd2000PCIe + { + public: + rhd2000PCIe(); + ~rhd2000PCIe(); + + enum AmplifierSampleRate { + SampleRate1000Hz, + SampleRate1250Hz, + SampleRate1500Hz, + SampleRate2000Hz, + SampleRate2500Hz, + SampleRate3000Hz, + SampleRate3333Hz, + SampleRate4000Hz, + SampleRate5000Hz, + SampleRate6250Hz, + SampleRate8000Hz, + SampleRate10000Hz, + SampleRate12500Hz, + SampleRate15000Hz, + SampleRate20000Hz, + SampleRate25000Hz, + SampleRate30000Hz + }; + + enum BoardDataSource { + PortA1 = 0, + PortA2 = 1, + PortB1 = 2, + PortB2 = 3, + PortC1 = 4, + PortC2 = 5, + PortD1 = 6, + PortD2 = 7, + PortA1Ddr = 8, + PortA2Ddr = 9, + PortB1Ddr = 10, + PortB2Ddr = 11, + PortC1Ddr = 12, + PortC2Ddr = 13, + PortD1Ddr = 14, + PortD2Ddr = 15 + }; + + enum AuxCmdSlot { + AuxCmd1, + AuxCmd2, + AuxCmd3 + }; + + enum BoardPort { + PortA, + PortB, + PortC, + PortD + }; + + bool open(); + void initialize(); + void resetBoard(); + + bool setSampleRate(AmplifierSampleRate newSampleRate); + double getSampleRate() const; + AmplifierSampleRate getSampleRateEnum() const; + + void uploadCommandList(const vector<int> &commandList, AuxCmdSlot auxCommandSlot, int bank); + void selectAuxCommandBank(BoardPort port, AuxCmdSlot auxCommandSlot, int bank); + void selectAuxCommandLength(AuxCmdSlot auxCommandSlot, int loopIndex, int endIndex); + + void setContinuousRunMode(bool continuousMode); + void setMaxTimeStep(unsigned int maxTimeStep); + + void run(); + bool isRunning() const; + + void setCableDelay(BoardPort port, int delay); + void setCableLengthMeters(BoardPort port, double lengthInMeters); + void setCableLengthFeet(BoardPort port, double lengthInFeet); + double estimateCableLengthMeters(int delay) const; + double estimateCableLengthFeet(int delay) const; + + void setDspSettle(bool enabled); + + void setDataSource(int stream, BoardDataSource dataSource); + void enableDataStream(int stream, bool enabled); + int getNumEnabledDataStreams() const; + + int getCableDelay(BoardPort port) const; + void getCableDelay(vector<int> &delays) const; + bool isStreamEnabled(int streamIndex); + + bool readRawDataBlock(unsigned char** bufferPtr, int nSamples = -1); + bool readDataBlock(Rhd2000DataBlock *dataBlock, int nSamples = -1); + bool readDataBlocks(int numBlocks, queue<Rhd2000DataBlock> &dataQueue); + + void flush(); + + + private: + int fidControl, fidStatus, fidFIFO; + + enum controlAddr { + ResetRun = 0x00, + MaxTimeStepLsb = 0x02, + MaxTimeStepMsb = 0x04, + DataFreqPll = 0x06, + MisoDelay = 0x08, + AuxCmdBank1 = 0x10, + AuxCmdBank2 = 0x12, + AuxCmdBank3 = 0x14, + AuxCmdLength1 = 0x16, + AuxCmdLength2 = 0x18, + AuxCmdLength3 = 0x1A, + AuxCmdLoop1 = 0x1C, + AuxCmdLoop2 = 0x1E, + AuxCmdLoop3 = 0x20, + DataStreamSel1234 = 0x24, + DataStreamSel5678 = 0x26, + DataStreamSel9ABC = 0x28, + DataStreamSelDEF10 = 0x2A, + DataStreamEn = 0x2C, + StartTrigger = 0x3E + }; + enum statusAddr { + NumWordsLsb = 0x00, + NumWordsMsb = 0x02, + SpiRunning = 0x04, + DataClkLocked = 0x08, + BoardId = 0x0A, + BoardVersion = 0x0C + }; + + AmplifierSampleRate sampleRate; + int numDataStreams; // total number of data streams currently enabled + int dataStreamEnabled[MAX_NUM_DATA_STREAMS]; // 0 (disabled) or 1 (enabled), set for maximum stream number + vector<int> cableDelay; + + void writeRegister(controlAddr reg, int16_t value, int16_t mask = 0xFFFF); + int16_t readRegister(statusAddr reg) const; + + bool isDcmProgDone() const; + bool isDataClockLocked() const; + + unsigned char dataBuffer[DATA_BUFFER_SIZE]; + + }; +}; +#endif // !RHD2000PCIE_H \ No newline at end of file diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.cpp b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.cpp new file mode 100644 index 000000000..9363fb196 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.cpp @@ -0,0 +1,390 @@ +//---------------------------------------------------------------------------------- +// rhd2000datablock.cpp +// +// Intan Technoloies RHD2000 Rhythm Interface API +// Rhd2000DataBlock Class +// Version 1.4 (26 February 2014) +// +// Copyright (c) 2013-2014 Intan Technologies LLC +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the +// use of this software. +// +// Permission is granted to anyone to use this software for any applications that +// use Intan Technologies integrated circuits, and to alter it and redistribute it +// freely. +// +// See http://www.intantech.com for documentation and product information. +//---------------------------------------------------------------------------------- + +#include <iostream> +#include <fstream> +#include <iomanip> +#include <vector> + +#include "rhd2000datablock.h" + +using namespace std; +using namespace PCIeRhythm; + +// This class creates a data structure storing SAMPLES_PER_DATA_BLOCK data frames +// from a Rhythm FPGA interface controlling up to eight RHD2000 chips. + +// Constructor. Allocates memory for data block. +Rhd2000DataBlock::Rhd2000DataBlock(int numDataStreams) : samplesPerBlock(SAMPLES_PER_DATA_BLOCK_PCIE) +{ + allocateUIntArray1D(timeStamp, samplesPerBlock); + allocateIntArray3D(amplifierData, numDataStreams, 32, samplesPerBlock); + allocateIntArray3D(auxiliaryData, numDataStreams, 3, samplesPerBlock); + allocateIntArray2D(boardAdcData, 8, samplesPerBlock); + allocateIntArray1D(ttlIn, samplesPerBlock); + allocateIntArray1D(ttlOut, samplesPerBlock); +} + +// Allocates memory for a 1-D array of integers. +void Rhd2000DataBlock::allocateIntArray1D(vector<int> &array1D, int xSize) +{ + array1D.resize(xSize); +} + +// Allocates memory for a 1-D array of unsigned integers. +void Rhd2000DataBlock::allocateUIntArray1D(vector<unsigned int> &array1D, int xSize) +{ + array1D.resize(xSize); +} + +// Allocates memory for a 2-D array of integers. +void Rhd2000DataBlock::allocateIntArray2D(vector<vector<int> > & array2D, int xSize, int ySize) +{ + int i; + + array2D.resize(xSize); + for (i = 0; i < xSize; ++i) + array2D[i].resize(ySize); +} + +// Allocates memory for a 3-D array of integers. +void Rhd2000DataBlock::allocateIntArray3D(vector<vector<vector<int> > > &array3D, int xSize, int ySize, int zSize) +{ + int i, j; + + array3D.resize(xSize); + for (i = 0; i < xSize; ++i) { + array3D[i].resize(ySize); + + for (j = 0; j < ySize; ++j) { + array3D[i][j].resize(zSize); + } + } +} + +// Returns the number of samples in a USB data block. +unsigned int Rhd2000DataBlock::getSamplesPerDataBlock() +{ + return SAMPLES_PER_DATA_BLOCK_PCIE; +} + +// Returns the number of 16-bit words in a USB data block with numDataStreams data streams enabled. +unsigned int Rhd2000DataBlock::calculateDataBlockSizeInWords(int numDataStreams, int nSamples) +{ + unsigned int samps = nSamples <= 0 ? SAMPLES_PER_DATA_BLOCK_PCIE : nSamples; + return samps * (4 + 2 + numDataStreams * 36 + 8 + 2); + // 4 = magic number; 2 = time stamp; 36 = (32 amp channels + 3 aux commands + 1 filler word); 8 = ADCs; 2 = TTL in/out +} + +// Check first 64 bits of USB header against the fixed Rhythm "magic number" to verify data sync. +bool Rhd2000DataBlock::checkUsbHeader(unsigned char usbBuffer[], int index) +{ + unsigned long long x1, x2, x3, x4, x5, x6, x7, x8; + unsigned long long header; + + x1 = usbBuffer[index]; + x2 = usbBuffer[index + 1]; + x3 = usbBuffer[index + 2]; + x4 = usbBuffer[index + 3]; + x5 = usbBuffer[index + 4]; + x6 = usbBuffer[index + 5]; + x7 = usbBuffer[index + 6]; + x8 = usbBuffer[index + 7]; + + header = (x8 << 56) + (x7 << 48) + (x6 << 40) + (x5 << 32) + (x4 << 24) + (x3 << 16) + (x2 << 8) + (x1 << 0); + + return (header == RHD2000_HEADER_MAGIC_NUMBER); +} + +// Read 32-bit time stamp from USB data frame. +unsigned int Rhd2000DataBlock::convertUsbTimeStamp(unsigned char usbBuffer[], int index) +{ + unsigned int x1, x2, x3, x4; + x1 = usbBuffer[index]; + x2 = usbBuffer[index + 1]; + x3 = usbBuffer[index + 2]; + x4 = usbBuffer[index + 3]; + + return (x4 << 24) + (x3 << 16) + (x2 << 8) + (x1 << 0); +} + +// Convert two USB bytes into 16-bit word. +int Rhd2000DataBlock::convertUsbWord(unsigned char usbBuffer[], int index) +{ + unsigned int x1, x2, result; + + x1 = (unsigned int) usbBuffer[index]; + x2 = (unsigned int) usbBuffer[index + 1]; + + result = (x2 << 8) | (x1 << 0); + + return (int) result; +} + +// Fill data block with raw data from USB input buffer. +void Rhd2000DataBlock::fillFromUsbBuffer(unsigned char usbBuffer[], int blockIndex, int numDataStreams, int nSamples) +{ + int index, t, channel, stream, i; + int samplesToRead = nSamples <= 0 ? samplesPerBlock : nSamples; + int num = 0; + + index = blockIndex * 2 * calculateDataBlockSizeInWords(numDataStreams, usb3); + for (t = 0; t < samplesToRead; ++t) { + if (!checkUsbHeader(usbBuffer, index)) { + cerr << "Error in Rhd2000EvalBoard::readDataBlock: Incorrect header." << endl; + break; + } + else + num++; + //else cerr << "Block ok" << endl; + index += 8; + timeStamp[t] = convertUsbTimeStamp(usbBuffer, index); + index += 4; + + // Read auxiliary results + for (channel = 0; channel < 3; ++channel) { + for (stream = 0; stream < numDataStreams; ++stream) { + auxiliaryData[stream][channel][t] = convertUsbWord(usbBuffer, index); + index += 2; + } + } + + // Read amplifier channels + for (channel = 0; channel < 32; ++channel) { + for (stream = 0; stream < numDataStreams; ++stream) { + amplifierData[stream][channel][t] = convertUsbWord(usbBuffer, index); + index += 2; + } + } + + // skip 36th filler word in each data stream + index += 2 * numDataStreams; + + // Read from AD5662 ADCs + for (i = 0; i < 8; ++i) { + boardAdcData[i][t] = convertUsbWord(usbBuffer, index); + index += 2; + } + + // Read TTL input and output values + ttlIn[t] = convertUsbWord(usbBuffer, index); + index += 2; + + ttlOut[t] = convertUsbWord(usbBuffer, index); + index += 2; + } + //cout << "Read " << num << " valid samples with " << numDataStreams << " streams. Usb mode status: " << usb3 << endl; +} + +// Print the contents of RHD2000 registers from a selected USB data stream (0-7) +// to the console. +void Rhd2000DataBlock::print(int stream) const +{ + const int RamOffset = 37; + + cout << endl; + cout << "RHD 2000 Data Block contents:" << endl; + cout << " ROM contents:" << endl; + cout << " Chip Name: " << + (char) auxiliaryData[stream][2][24] << + (char) auxiliaryData[stream][2][25] << + (char) auxiliaryData[stream][2][26] << + (char) auxiliaryData[stream][2][27] << + (char) auxiliaryData[stream][2][28] << + (char) auxiliaryData[stream][2][29] << + (char) auxiliaryData[stream][2][30] << + (char) auxiliaryData[stream][2][31] << endl; + cout << " Company Name:" << + (char) auxiliaryData[stream][2][32] << + (char) auxiliaryData[stream][2][33] << + (char) auxiliaryData[stream][2][34] << + (char) auxiliaryData[stream][2][35] << + (char) auxiliaryData[stream][2][36] << endl; + cout << " Intan Chip ID: " << auxiliaryData[stream][2][19] << endl; + cout << " Number of Amps: " << auxiliaryData[stream][2][20] << endl; + cout << " Unipolar/Bipolar Amps: "; + switch (auxiliaryData[stream][2][21]) { + case 0: + cout << "bipolar"; + break; + case 1: + cout << "unipolar"; + break; + default: + cout << "UNKNOWN"; + } + cout << endl; + cout << " Die Revision: " << auxiliaryData[stream][2][22] << endl; + cout << " Future Expansion Register: " << auxiliaryData[stream][2][23] << endl; + + cout << " RAM contents:" << endl; + cout << " ADC reference BW: " << ((auxiliaryData[stream][2][RamOffset + 0] & 0xc0) >> 6) << endl; + cout << " amp fast settle: " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x20) >> 5) << endl; + cout << " amp Vref enable: " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x10) >> 4) << endl; + cout << " ADC comparator bias: " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x0c) >> 2) << endl; + cout << " ADC comparator select: " << ((auxiliaryData[stream][2][RamOffset + 0] & 0x03) >> 0) << endl; + cout << " VDD sense enable: " << ((auxiliaryData[stream][2][RamOffset + 1] & 0x40) >> 6) << endl; + cout << " ADC buffer bias: " << ((auxiliaryData[stream][2][RamOffset + 1] & 0x3f) >> 0) << endl; + cout << " MUX bias: " << ((auxiliaryData[stream][2][RamOffset + 2] & 0x3f) >> 0) << endl; + cout << " MUX load: " << ((auxiliaryData[stream][2][RamOffset + 3] & 0xe0) >> 5) << endl; + cout << " tempS2, tempS1: " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x10) >> 4) << "," << + ((auxiliaryData[stream][2][RamOffset + 3] & 0x08) >> 3) << endl; + cout << " tempen: " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x04) >> 2) << endl; + cout << " digout HiZ: " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x02) >> 1) << endl; + cout << " digout: " << ((auxiliaryData[stream][2][RamOffset + 3] & 0x01) >> 0) << endl; + cout << " weak MISO: " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x80) >> 7) << endl; + cout << " twoscomp: " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x40) >> 6) << endl; + cout << " absmode: " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x20) >> 5) << endl; + cout << " DSPen: " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x10) >> 4) << endl; + cout << " DSP cutoff freq: " << ((auxiliaryData[stream][2][RamOffset + 4] & 0x0f) >> 0) << endl; + cout << " Zcheck DAC power: " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x40) >> 6) << endl; + cout << " Zcheck load: " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x20) >> 5) << endl; + cout << " Zcheck scale: " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x18) >> 3) << endl; + cout << " Zcheck conn all: " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x04) >> 2) << endl; + cout << " Zcheck sel pol: " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x02) >> 1) << endl; + cout << " Zcheck en: " << ((auxiliaryData[stream][2][RamOffset + 5] & 0x01) >> 0) << endl; + cout << " Zcheck DAC: " << ((auxiliaryData[stream][2][RamOffset + 6] & 0xff) >> 0) << endl; + cout << " Zcheck select: " << ((auxiliaryData[stream][2][RamOffset + 7] & 0x3f) >> 0) << endl; + cout << " ADC aux1 en: " << ((auxiliaryData[stream][2][RamOffset + 9] & 0x80) >> 7) << endl; + cout << " ADC aux2 en: " << ((auxiliaryData[stream][2][RamOffset + 11] & 0x80) >> 7) << endl; + cout << " ADC aux3 en: " << ((auxiliaryData[stream][2][RamOffset + 13] & 0x80) >> 7) << endl; + cout << " offchip RH1: " << ((auxiliaryData[stream][2][RamOffset + 8] & 0x80) >> 7) << endl; + cout << " offchip RH2: " << ((auxiliaryData[stream][2][RamOffset + 10] & 0x80) >> 7) << endl; + cout << " offchip RL: " << ((auxiliaryData[stream][2][RamOffset + 12] & 0x80) >> 7) << endl; + + int rH1Dac1 = auxiliaryData[stream][2][RamOffset + 8] & 0x3f; + int rH1Dac2 = auxiliaryData[stream][2][RamOffset + 9] & 0x1f; + int rH2Dac1 = auxiliaryData[stream][2][RamOffset + 10] & 0x3f; + int rH2Dac2 = auxiliaryData[stream][2][RamOffset + 11] & 0x1f; + int rLDac1 = auxiliaryData[stream][2][RamOffset + 12] & 0x7f; + int rLDac2 = auxiliaryData[stream][2][RamOffset + 13] & 0x3f; + int rLDac3 = auxiliaryData[stream][2][RamOffset + 13] & 0x40 >> 6; + + double rH1 = 2630.0 + rH1Dac2 * 30800.0 + rH1Dac1 * 590.0; + double rH2 = 8200.0 + rH2Dac2 * 38400.0 + rH2Dac1 * 730.0; + double rL = 3300.0 + rLDac3 * 3000000.0 + rLDac2 * 15400.0 + rLDac1 * 190.0; + + cout << fixed << setprecision(2); + + cout << " RH1 DAC1, DAC2: " << rH1Dac1 << " " << rH1Dac2 << " = " << (rH1 / 1000) << + " kOhm" << endl; + cout << " RH2 DAC1, DAC2: " << rH2Dac1 << " " << rH2Dac2 << " = " << (rH2 / 1000) << + " kOhm" << endl; + cout << " RL DAC1, DAC2, DAC3: " << rLDac1 << " " << rLDac2 << " " << rLDac3 << " = " << + (rL / 1000) << " kOhm" << endl; + + cout << " amp power[31:0]: " << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x80) >> 7) << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x40) >> 6) << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x20) >> 5) << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x10) >> 4) << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x08) >> 3) << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x04) >> 2) << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x02) >> 1) << + ((auxiliaryData[stream][2][RamOffset + 17] & 0x01) >> 0) << " " << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x80) >> 7) << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x40) >> 6) << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x20) >> 5) << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x10) >> 4) << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x08) >> 3) << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x04) >> 2) << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x02) >> 1) << + ((auxiliaryData[stream][2][RamOffset + 16] & 0x01) >> 0) << " " << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x80) >> 7) << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x40) >> 6) << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x20) >> 5) << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x10) >> 4) << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x08) >> 3) << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x04) >> 2) << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x02) >> 1) << + ((auxiliaryData[stream][2][RamOffset + 15] & 0x01) >> 0) << " " << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x80) >> 7) << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x40) >> 6) << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x20) >> 5) << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x10) >> 4) << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x08) >> 3) << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x04) >> 2) << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x02) >> 1) << + ((auxiliaryData[stream][2][RamOffset + 14] & 0x01) >> 0) << endl; + + cout << endl; + + int tempA = auxiliaryData[stream][1][12]; + int tempB = auxiliaryData[stream][1][20]; + int vddSample = auxiliaryData[stream][1][28]; + + double tempUnitsC = ((double)(tempB - tempA)) / 98.9 - 273.15; + double tempUnitsF = (9.0/5.0) * tempUnitsC + 32.0; + + double vddSense = 0.0000748 * ((double) vddSample); + + cout << setprecision(1); + cout << " Temperature sensor (only one reading): " << tempUnitsC << " C (" << + tempUnitsF << " F)" << endl; + + cout << setprecision(2); + cout << " Supply voltage sensor : " << vddSense << " V" << endl; + + cout << setprecision(6); + cout.unsetf(ios::floatfield); + cout << endl; +} + +// Write a 16-bit dataWord to an outputStream in "little endian" format (i.e., least significant +// byte first). We must do this explicitly for cross-platform consistency. For example, Windows +// is a little-endian OS, while Mac OS X and Linux can be little-endian or big-endian depending on +// the processor running the operating system. +// +// (See "Endianness" article in Wikipedia for more information.) +void Rhd2000DataBlock::writeWordLittleEndian(ofstream &outputStream, int dataWord) const +{ + unsigned short msb, lsb; + + lsb = ((unsigned short) dataWord) & 0x00ff; + msb = (((unsigned short) dataWord) & 0xff00) >> 8; + + outputStream << (unsigned char) lsb; + outputStream << (unsigned char) msb; +} + +// Write contents of data block to a binary output stream (saveOut) in little endian format. +void Rhd2000DataBlock::write(ofstream &saveOut, int numDataStreams) const +{ + int t, channel, stream, i; + + for (t = 0; t < samplesPerBlock; ++t) { + writeWordLittleEndian(saveOut, timeStamp[t]); + for (channel = 0; channel < 32; ++channel) { + for (stream = 0; stream < numDataStreams; ++stream) { + writeWordLittleEndian(saveOut, amplifierData[stream][channel][t]); + } + } + for (channel = 0; channel < 3; ++channel) { + for (stream = 0; stream < numDataStreams; ++stream) { + writeWordLittleEndian(saveOut, auxiliaryData[stream][channel][t]); + } + } + for (i = 0; i < 8; ++i) { + writeWordLittleEndian(saveOut, boardAdcData[i][t]); + } + writeWordLittleEndian(saveOut, ttlIn[t]); + writeWordLittleEndian(saveOut, ttlOut[t]); + } +} diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.h b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.h new file mode 100644 index 000000000..b126b4f51 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000datablock.h @@ -0,0 +1,68 @@ +//---------------------------------------------------------------------------------- +// rhd2000datablock.h +// +// Intan Technoloies RHD2000 Rhythm Interface API +// Rhd2000DataBlock Class Header File +// Version 1.4 (26 February 2014) +// +// Copyright (c) 2013-2014 Intan Technologies LLC +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the +// use of this software. +// +// Permission is granted to anyone to use this software for any applications that +// use Intan Technologies integrated circuits, and to alter it and redistribute it +// freely. +// +// See http://www.intantech.com for documentation and product information. +//---------------------------------------------------------------------------------- + +#ifndef RHD2000DATABLOCK_H +#define RHD2000DATABLOCK_H + +#define SAMPLES_PER_DATA_BLOCK_PCIE 16 +#define RHD2000_HEADER_MAGIC_NUMBER 0xc691199927021942 + +using namespace std; + +namespace PCIeRhythm { + + class Rhd2000EvalBoard; + + class Rhd2000DataBlock + { + public: + Rhd2000DataBlock(int numDataStreams); + + vector<unsigned int> timeStamp; + vector<vector<vector<int> > > amplifierData; + vector<vector<vector<int> > > auxiliaryData; + vector<vector<int> > boardAdcData; + vector<int> ttlIn; + vector<int> ttlOut; + + static unsigned int calculateDataBlockSizeInWords(int numDataStreams, int nSamples = -1); + static unsigned int getSamplesPerDataBlock(); + void fillFromUsbBuffer(unsigned char usbBuffer[], int blockIndex, int numDataStreams, int nSamples = -1); + void print(int stream) const; + void write(ofstream &saveOut, int numDataStreams) const; + + static bool checkUsbHeader(unsigned char usbBuffer[], int index); + static unsigned int convertUsbTimeStamp(unsigned char usbBuffer[], int index); + static int convertUsbWord(unsigned char usbBuffer[], int index); + + private: + void allocateIntArray3D(vector<vector<vector<int> > > &array3D, int xSize, int ySize, int zSize); + void allocateIntArray2D(vector<vector<int> > &array2D, int xSize, int ySize); + void allocateIntArray1D(vector<int> &array1D, int xSize); + void allocateUIntArray1D(vector<unsigned int> &array1D, int xSize); + + void writeWordLittleEndian(ofstream &outputStream, int dataWord) const; + + + const unsigned int samplesPerBlock; + bool usb3; + }; +}; +#endif // RHD2000DATABLOCK_H diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.cpp b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.cpp new file mode 100644 index 000000000..8fc819c5d --- /dev/null +++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.cpp @@ -0,0 +1,1025 @@ +//---------------------------------------------------------------------------------- +// rhd2000registers.cpp +// +// Intan Technoloies RHD2000 Rhythm Interface API +// Rhd2000Registers Class +// Version 1.4 (26 February 2014) +// +// Copyright (c) 2013-2014 Intan Technologies LLC +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the +// use of this software. +// +// Permission is granted to anyone to use this software for any applications that +// use Intan Technologies integrated circuits, and to alter it and redistribute it +// freely. +// +// See http://www.intantech.com for documentation and product information. +//---------------------------------------------------------------------------------- + +#include <iostream> +#include <iomanip> +#include <cmath> +#include <vector> +#include <queue> + +#include "rhd2000registers.h" + +using namespace std; +using namespace PCIeRhythm; + +// This class creates and manages a data structure representing the internal RAM registers on +// a RHD2000 chip, and generates command lists to configure the chip and perform other functions. +// Changing the value of variables within an instance of this class does not directly affect a +// RHD2000 chip connected to the FPGA; rather, a command list must be generated from this class +// and then downloaded to the FPGA board using Rhd2000EvalBoard::uploadCommandList. + +// Constructor. Set RHD2000 register variables to default values. +Rhd2000Registers::Rhd2000Registers(double sampleRate) +{ + aPwr.resize(64); + + defineSampleRate(sampleRate); + + // Set default values for all register settings + adcReferenceBw = 3; // ADC reference generator bandwidth (0 [highest BW] - 3 [lowest BW]); + // always set to 3 + setFastSettle(false); // amplifier fast settle (off = normal operation) + ampVrefEnable = 1; // enable amplifier voltage references (0 = power down; 1 = enable); + // 1 = normal operation + adcComparatorBias = 3; // ADC comparator preamp bias current (0 [lowest] - 3 [highest], only + // valid for comparator select = 2,3); always set to 3 + adcComparatorSelect = 2; // ADC comparator select; always set to 2 + + vddSenseEnable = 1; // supply voltage sensor enable (0 = disable; 1 = enable) + // adcBufferBias = 32; // ADC reference buffer bias current (0 [highest current] - 63 [lowest current]); + // This value should be set according to ADC sampling rate; set in setSampleRate() + + // muxBias = 40; // ADC input MUX bias current (0 [highest current] - 63 [lowest current]); + // This value should be set according to ADC sampling rate; set in setSampleRate() + + // muxLoad = 0; // MUX capacitance load at ADC input (0 [min CL] - 7 [max CL]); LSB = 3 pF + // Set in setSampleRate() + + tempS1 = 0; // temperature sensor S1 (0-1); 0 = power saving mode when temperature sensor is + // not in use + tempS2 = 0; // temperature sensor S2 (0-1); 0 = power saving mode when temperature sensor is + // not in use + tempEn = 0; // temperature sensor enable (0 = disable; 1 = enable) + setDigOutHiZ(); // auxiliary digital output state + + weakMiso = 1; // weak MISO (0 = MISO line is HiZ when CS is inactive; 1 = MISO line is weakly + // driven when CS is inactive) + twosComp = 0; // two's complement ADC results (0 = unsigned offset representation; 1 = signed + // representation) + absMode = 0; // absolute value mode (0 = normal output; 1 = output passed through abs(x) function) + enableDsp(true); // DSP offset removal enable/disable + setDspCutoffFreq(1.0); // DSP offset removal HPF cutoff freqeuncy + + zcheckDacPower = 1; // impedance testing DAC power-up (0 = power down; 1 = power up) + zcheckLoad = 0; // impedance testing dummy load (0 = normal operation; 1 = insert 60 pF to ground) + setZcheckScale(ZcheckCs100fF); // impedance testing scale factor (100 fF, 1.0 pF, or 10.0 pF) + zcheckConnAll = 0; // impedance testing connect all (0 = normal operation; 1 = connect all electrodes together) + setZcheckPolarity(ZcheckPositiveInput); // impedance testing polarity select (RHD2216 only) (0 = test positive inputs; + // 1 = test negative inputs) + enableZcheck(false); // impedance testing enable/disable + + setZcheckChannel(0); // impedance testing amplifier select (0-63) + + offChipRH1 = 0; // bandwidth resistor RH1 on/off chip (0 = on chip; 1 = off chip) + offChipRH2 = 0; // bandwidth resistor RH2 on/off chip (0 = on chip; 1 = off chip) + offChipRL = 0; // bandwidth resistor RL on/off chip (0 = on chip; 1 = off chip) + adcAux1En = 1; // enable ADC aux1 input (when RH1 is on chip) (0 = disable; 1 = enable) + adcAux2En = 1; // enable ADC aux2 input (when RH2 is on chip) (0 = disable; 1 = enable) + adcAux3En = 1; // enable ADC aux3 input (when RL is on chip) (0 = disable; 1 = enable) + + setUpperBandwidth(10000.0); // set upper bandwidth of amplifiers + setLowerBandwidth(1.0); // set lower bandwidth of amplifiers + + powerUpAllAmps(); // turn on all amplifiers +} + +// Define RHD2000 per-channel sampling rate so that certain sampling-rate-dependent registers are set correctly +// (This function does not change the sampling rate of the FPGA; for this, use Rhd2000EvalBoard::setSampleRate.) +void Rhd2000Registers::defineSampleRate(double newSampleRate) +{ + sampleRate = newSampleRate; + + muxLoad = 0; + + if (sampleRate < 3334.0) { + muxBias = 40; + adcBufferBias = 32; + } else if (sampleRate < 4001.0) { + muxBias = 40; + adcBufferBias = 16; + } else if (sampleRate < 5001.0) { + muxBias = 40; + adcBufferBias = 8; + } else if (sampleRate < 6251.0) { + muxBias = 32; + adcBufferBias = 8; + } else if (sampleRate < 8001.0) { + muxBias = 26; + adcBufferBias = 8; + } else if (sampleRate < 10001.0) { + muxBias = 18; + adcBufferBias = 4; + } else if (sampleRate < 12501.0) { + muxBias = 16; + adcBufferBias = 3; + } else if (sampleRate < 15001.0) { + muxBias = 7; + adcBufferBias = 3; + } else { + muxBias = 4; + adcBufferBias = 2; + } +} + +// Enable or disable amplifier fast settle function; drive amplifiers to baseline +// if enabled. +void Rhd2000Registers::setFastSettle(bool enabled) +{ + ampFastSettle = (enabled ? 1 : 0); +} + +// Drive auxiliary digital output low +void Rhd2000Registers::setDigOutLow() +{ + digOut = 0; + digOutHiZ = 0; +} + +// Drive auxiliary digital output high +void Rhd2000Registers::setDigOutHigh() +{ + digOut = 1; + digOutHiZ = 0; +} + +// Set auxiliary digital output to high-impedance (HiZ) state +void Rhd2000Registers::setDigOutHiZ() +{ + digOut = 0; + digOutHiZ = 1; +} + +// Enable or disable ADC auxiliary input 1 +void Rhd2000Registers::enableAux1(bool enabled) +{ + adcAux1En = (enabled ? 1 : 0); +} + +// Enable or disable ADC auxiliary input 2 +void Rhd2000Registers::enableAux2(bool enabled) +{ + adcAux2En = (enabled ? 1 : 0); +} + +// Enable or disable ADC auxiliary input 3 +void Rhd2000Registers::enableAux3(bool enabled) +{ + adcAux3En = (enabled ? 1 : 0); +} + +// Enable or disable DSP offset removal filter +void Rhd2000Registers::enableDsp(bool enabled) +{ + dspEn = (enabled ? 1 : 0); +} + +// Set the DSP offset removal filter cutoff frequency as closely to the requested +// newDspCutoffFreq (in Hz) as possible; returns the actual cutoff frequency (in Hz). +double Rhd2000Registers::setDspCutoffFreq(double newDspCutoffFreq) +{ + int n; + double x, fCutoff[16], logNewDspCutoffFreq, logFCutoff[16], minLogDiff; + const double Pi = 2*acos(0.0); + + fCutoff[0] = 0.0; // We will not be using fCutoff[0], but we initialize it to be safe + + logNewDspCutoffFreq = log10(newDspCutoffFreq); + + // Generate table of all possible DSP cutoff frequencies + for (n = 1; n < 16; ++n) { + x = pow(2.0, (double) n); + fCutoff[n] = sampleRate * log(x / (x - 1.0)) / (2*Pi); + logFCutoff[n] = log10(fCutoff[n]); + // cout << " fCutoff[" << n << "] = " << fCutoff[n] << " Hz" << endl; + } + + // Now find the closest value to the requested cutoff frequency (on a logarithmic scale) + if (newDspCutoffFreq > fCutoff[1]) { + dspCutoffFreq = 1; + } else if (newDspCutoffFreq < fCutoff[15]) { + dspCutoffFreq = 15; + } else { + minLogDiff = 10000000.0; + for (n = 1; n < 16; ++n) { + if (abs(logNewDspCutoffFreq - logFCutoff[n]) < minLogDiff) { + minLogDiff = abs(logNewDspCutoffFreq - logFCutoff[n]); + dspCutoffFreq = n; + } + } + } + + return fCutoff[dspCutoffFreq]; +} + +// Returns the current value of the DSP offset removal cutoff frequency (in Hz). +double Rhd2000Registers::getDspCutoffFreq() const +{ + double x; + const double Pi = 2*acos(0.0); + + x = pow(2.0, (double) dspCutoffFreq); + + return sampleRate * log(x / (x - 1.0)) / (2*Pi); +} + +// Enable or disable impedance checking mode +void Rhd2000Registers::enableZcheck(bool enabled) +{ + zcheckEn = (enabled ? 1: 0); +} + +// Power up or down impedance checking DAC +void Rhd2000Registers::setZcheckDacPower(bool enabled) +{ + zcheckDacPower = (enabled ? 1 : 0); +} + +// Select the series capacitor used to convert the voltage waveform generated by the on-chip +// DAC into an AC current waveform that stimulates a selected electrode for impedance testing +// (ZcheckCs100fF, ZcheckCs1pF, or Zcheck10pF). +void Rhd2000Registers::setZcheckScale(ZcheckCs scale) +{ + switch (scale) { + case ZcheckCs100fF: + zcheckScale = 0x00; // Cs = 0.1 pF + break; + case ZcheckCs1pF: + zcheckScale = 0x01; // Cs = 1.0 pF + break; + case ZcheckCs10pF: + zcheckScale = 0x03; // Cs = 10.0 pF + break; + } +} + +// Select impedance testing of positive or negative amplifier inputs (RHD2216 only), based +// on the variable polarity (ZcheckPositiveInput or ZcheckNegativeInput) +void Rhd2000Registers::setZcheckPolarity(ZcheckPolarity polarity) +{ + switch (polarity) { + case ZcheckPositiveInput: + zcheckSelPol = 0; + break; + case ZcheckNegativeInput: + zcheckSelPol = 1; + break; + } +} + +// Select the amplifier channel (0-63) for impedance testing. +int Rhd2000Registers::setZcheckChannel(int channel) +{ + if (channel < 0 || channel > 63) { + return -1; + } else { + zcheckSelect = channel; + return zcheckSelect; + } +} + +// Power up or down selected amplifier on chip +void Rhd2000Registers::setAmpPowered(int channel, bool powered) +{ + if (channel >= 0 && channel <= 63) { + aPwr[channel] = (powered ? 1 : 0); + } +} + +// Power up all amplifiers on chip +void Rhd2000Registers::powerUpAllAmps() +{ + for (int channel = 0; channel < 64; ++channel) { + aPwr[channel] = 1; + } +} + +// Power down all amplifiers on chip +void Rhd2000Registers::powerDownAllAmps() +{ + for (int channel = 0; channel < 64; ++channel) { + aPwr[channel] = 0; + } +} + +// Returns the value of a selected RAM register (0-17) on the RHD2000 chip, based +// on the current register variables in the class instance. +int Rhd2000Registers::getRegisterValue(int reg) const +{ + int regout; + const int zcheckDac = 128; // midrange + + switch (reg) { + case 0: + regout = (adcReferenceBw << 6) + (ampFastSettle << 5) + (ampVrefEnable << 4) + + (adcComparatorBias << 2) + adcComparatorSelect; + break; + case 1: + regout = (vddSenseEnable << 6) + adcBufferBias; + break; + case 2: + regout = muxBias; + break; + case 3: + regout = (muxLoad << 5) + (tempS2 << 4) + (tempS1 << 3) + (tempEn << 2) + + (digOutHiZ << 1) + digOut; + break; + case 4: + regout = (weakMiso << 7) + (twosComp << 6) + (absMode << 5) + (dspEn << 4) + + dspCutoffFreq; + break; + case 5: + regout = (zcheckDacPower << 6) + (zcheckLoad << 5) + (zcheckScale << 3) + + (zcheckConnAll << 2) + (zcheckSelPol << 1) + zcheckEn; + break; + case 6: + regout = zcheckDac; + break; + case 7: + regout = zcheckSelect; + break; + case 8: + regout = (offChipRH1 << 7) + rH1Dac1; + break; + case 9: + regout = (adcAux1En << 7) + rH1Dac2; + break; + case 10: + regout = (offChipRH2 << 7) + rH2Dac1; + break; + case 11: + regout = (adcAux2En << 7) + rH2Dac2; + break; + case 12: + regout = (offChipRL << 7) + rLDac1; + break; + case 13: + regout = (adcAux3En << 7) + (rLDac3 << 6) + rLDac2; + break; + case 14: + regout = (aPwr[7] << 7) + (aPwr[6] << 6) + (aPwr[5] << 5) + (aPwr[4] << 4) + + (aPwr[3] << 3) + (aPwr[2] << 2) + (aPwr[1] << 1) + aPwr[0]; + break; + case 15: + regout = (aPwr[15] << 7) + (aPwr[14] << 6) + (aPwr[13] << 5) + (aPwr[12] << 4) + + (aPwr[11] << 3) + (aPwr[10] << 2) + (aPwr[9] << 1) + aPwr[0]; + break; + case 16: + regout = (aPwr[23] << 7) + (aPwr[22] << 6) + (aPwr[21] << 5) + (aPwr[20] << 4) + + (aPwr[19] << 3) + (aPwr[18] << 2) + (aPwr[17] << 1) + aPwr[16]; + break; + case 17: + regout = (aPwr[31] << 7) + (aPwr[30] << 6) + (aPwr[29] << 5) + (aPwr[28] << 4) + + (aPwr[27] << 3) + (aPwr[26] << 2) + (aPwr[25] << 1) + aPwr[24]; + break; + case 18: + regout = (aPwr[39] << 7) + (aPwr[38] << 6) + (aPwr[37] << 5) + (aPwr[36] << 4) + + (aPwr[35] << 3) + (aPwr[34] << 2) + (aPwr[33] << 1) + aPwr[32]; + break; + case 19: + regout = (aPwr[47] << 7) + (aPwr[46] << 6) + (aPwr[45] << 5) + (aPwr[44] << 4) + + (aPwr[43] << 3) + (aPwr[42] << 2) + (aPwr[41] << 1) + aPwr[40]; + break; + case 20: + regout = (aPwr[55] << 7) + (aPwr[54] << 6) + (aPwr[53] << 5) + (aPwr[52] << 4) + + (aPwr[51] << 3) + (aPwr[50] << 2) + (aPwr[49] << 1) + aPwr[48]; + break; + case 21: + regout = (aPwr[63] << 7) + (aPwr[62] << 6) + (aPwr[61] << 5) + (aPwr[60] << 4) + + (aPwr[59] << 3) + (aPwr[58] << 2) + (aPwr[57] << 1) + aPwr[56]; + break; + default: + regout = -1; + } + return regout; +} + +// Returns the value of the RH1 resistor (in ohms) corresponding to a particular upper +// bandwidth value (in Hz). +double Rhd2000Registers::rH1FromUpperBandwidth(double upperBandwidth) const +{ + double log10f = log10(upperBandwidth); + + return 0.9730 * pow(10.0, (8.0968 - 1.1892 * log10f + 0.04767 * log10f * log10f)); +} + +// Returns the value of the RH2 resistor (in ohms) corresponding to a particular upper +// bandwidth value (in Hz). +double Rhd2000Registers::rH2FromUpperBandwidth(double upperBandwidth) const +{ + double log10f = log10(upperBandwidth); + + return 1.0191 * pow(10.0, (8.1009 - 1.0821 * log10f + 0.03383 * log10f * log10f)); +} + +// Returns the value of the RL resistor (in ohms) corresponding to a particular lower +// bandwidth value (in Hz). +double Rhd2000Registers::rLFromLowerBandwidth(double lowerBandwidth) const +{ + double log10f = log10(lowerBandwidth); + + if (lowerBandwidth < 4.0) { + return 1.0061 * pow(10.0, (4.9391 - 1.2088 * log10f + 0.5698 * log10f * log10f + + 0.1442 * log10f * log10f * log10f)); + } else { + return 1.0061 * pow(10.0, (4.7351 - 0.5916 * log10f + 0.08482 * log10f * log10f)); + } +} + +// Returns the amplifier upper bandwidth (in Hz) corresponding to a particular value +// of the resistor RH1 (in ohms). +double Rhd2000Registers::upperBandwidthFromRH1(double rH1) const +{ + double a, b, c; + + a = 0.04767; + b = -1.1892; + c = 8.0968 - log10(rH1/0.9730); + + return pow(10.0, ((-b - sqrt(b * b - 4 * a * c))/(2 * a))); +} + +// Returns the amplifier upper bandwidth (in Hz) corresponding to a particular value +// of the resistor RH2 (in ohms). +double Rhd2000Registers::upperBandwidthFromRH2(double rH2) const +{ + double a, b, c; + + a = 0.03383; + b = -1.0821; + c = 8.1009 - log10(rH2/1.0191); + + return pow(10.0, ((-b - sqrt(b * b - 4 * a * c))/(2 * a))); +} + +// Returns the amplifier lower bandwidth (in Hz) corresponding to a particular value +// of the resistor RL (in ohms). +double Rhd2000Registers::lowerBandwidthFromRL(double rL) const +{ + double a, b, c; + + // Quadratic fit below is invalid for values of RL less than 5.1 kOhm + if (rL < 5100.0) { + rL = 5100.0; + } + + if (rL < 30000.0) { + a = 0.08482; + b = -0.5916; + c = 4.7351 - log10(rL/1.0061); + } else { + a = 0.3303; + b = -1.2100; + c = 4.9873 - log10(rL/1.0061); + } + + return pow(10.0, ((-b - sqrt(b * b - 4 * a * c))/(2 * a))); +} + +// Sets the on-chip RH1 and RH2 DAC values appropriately to set a particular amplifier +// upper bandwidth (in Hz). Returns an estimate of the actual upper bandwidth achieved. +double Rhd2000Registers::setUpperBandwidth(double upperBandwidth) +{ + const double RH1Base = 2200.0; + const double RH1Dac1Unit = 600.0; + const double RH1Dac2Unit = 29400.0; + const int RH1Dac1Steps = 63; + const int RH1Dac2Steps = 31; + + const double RH2Base = 8700.0; + const double RH2Dac1Unit = 763.0; + const double RH2Dac2Unit = 38400.0; + const int RH2Dac1Steps = 63; + const int RH2Dac2Steps = 31; + + double actualUpperBandwidth; + double rH1Target, rH2Target; + double rH1Actual, rH2Actual; + int i; + + // Upper bandwidths higher than 30 kHz don't work well with the RHD2000 amplifiers + if (upperBandwidth > 30000.0) { + upperBandwidth = 30000.0; + } + + rH1Target = rH1FromUpperBandwidth(upperBandwidth); + + rH1Dac1 = 0; + rH1Dac2 = 0; + rH1Actual = RH1Base; + + for (i = 0; i < RH1Dac2Steps; ++i) { + if (rH1Actual < rH1Target - (RH1Dac2Unit - RH1Dac1Unit / 2)) { + rH1Actual += RH1Dac2Unit; + ++rH1Dac2; + } + } + + for (i = 0; i < RH1Dac1Steps; ++i) { + if (rH1Actual < rH1Target - (RH1Dac1Unit / 2)) { + rH1Actual += RH1Dac1Unit; + ++rH1Dac1; + } + } + + rH2Target = rH2FromUpperBandwidth(upperBandwidth); + + rH2Dac1 = 0; + rH2Dac2 = 0; + rH2Actual = RH2Base; + + for (i = 0; i < RH2Dac2Steps; ++i) { + if (rH2Actual < rH2Target - (RH2Dac2Unit - RH2Dac1Unit / 2)) { + rH2Actual += RH2Dac2Unit; + ++rH2Dac2; + } + } + + for (i = 0; i < RH2Dac1Steps; ++i) { + if (rH2Actual < rH2Target - (RH2Dac1Unit / 2)) { + rH2Actual += RH2Dac1Unit; + ++rH2Dac1; + } + } + + double actualUpperBandwidth1, actualUpperBandwidth2; + + actualUpperBandwidth1 = upperBandwidthFromRH1(rH1Actual); + actualUpperBandwidth2 = upperBandwidthFromRH2(rH2Actual); + + // Upper bandwidth estimates calculated from actual RH1 value and acutal RH2 value + // should be very close; we will take their geometric mean to get a single + // number. + actualUpperBandwidth = sqrt(actualUpperBandwidth1 * actualUpperBandwidth2); + + /* + cout << endl; + cout << "Rhd2000Registers::setUpperBandwidth" << endl; + cout << fixed << setprecision(1); + + cout << "RH1 DAC2 = " << rH1Dac2 << ", DAC1 = " << rH1Dac1 << endl; + cout << "RH1 target: " << rH1Target << " Ohms" << endl; + cout << "RH1 actual: " << rH1Actual << " Ohms" << endl; + + cout << "RH2 DAC2 = " << rH2Dac2 << ", DAC1 = " << rH2Dac1 << endl; + cout << "RH2 target: " << rH2Target << " Ohms" << endl; + cout << "RH2 actual: " << rH2Actual << " Ohms" << endl; + + cout << "Upper bandwidth target: " << upperBandwidth << " Hz" << endl; + cout << "Upper bandwidth actual: " << actualUpperBandwidth << " Hz" << endl; + + cout << endl; + cout << setprecision(6); + cout.unsetf(ios::floatfield); + */ + + return actualUpperBandwidth; +} + +// Sets the on-chip RL DAC values appropriately to set a particular amplifier +// lower bandwidth (in Hz). Returns an estimate of the actual lower bandwidth achieved. +double Rhd2000Registers::setLowerBandwidth(double lowerBandwidth) +{ + const double RLBase = 3500.0; + const double RLDac1Unit = 175.0; + const double RLDac2Unit = 12700.0; + const double RLDac3Unit = 3000000.0; + const int RLDac1Steps = 127; + const int RLDac2Steps = 63; + + double actualLowerBandwidth; + double rLTarget; + double rLActual; + int i; + + // Lower bandwidths higher than 1.5 kHz don't work well with the RHD2000 amplifiers + if (lowerBandwidth > 1500.0) { + lowerBandwidth = 1500.0; + } + + rLTarget = rLFromLowerBandwidth(lowerBandwidth); + + rLDac1 = 0; + rLDac2 = 0; + rLDac3 = 0; + rLActual = RLBase; + + if (lowerBandwidth < 0.15) { + rLActual += RLDac3Unit; + ++rLDac3; + } + + for (i = 0; i < RLDac2Steps; ++i) { + if (rLActual < rLTarget - (RLDac2Unit - RLDac1Unit / 2)) { + rLActual += RLDac2Unit; + ++rLDac2; + } + } + + for (i = 0; i < RLDac1Steps; ++i) { + if (rLActual < rLTarget - (RLDac1Unit / 2)) { + rLActual += RLDac1Unit; + ++rLDac1; + } + } + + actualLowerBandwidth = lowerBandwidthFromRL(rLActual); + + /* + cout << endl; + cout << fixed << setprecision(1); + cout << "Rhd2000Registers::setLowerBandwidth" << endl; + + cout << "RL DAC3 = " << rLDac3 << ", DAC2 = " << rLDac2 << ", DAC1 = " << rLDac1 << endl; + cout << "RL target: " << rLTarget << " Ohms" << endl; + cout << "RL actual: " << rLActual << " Ohms" << endl; + + cout << setprecision(3); + + cout << "Lower bandwidth target: " << lowerBandwidth << " Hz" << endl; + cout << "Lower bandwidth actual: " << actualLowerBandwidth << " Hz" << endl; + + cout << endl; + cout << setprecision(6); + cout.unsetf(ios::floatfield); + */ + + return actualLowerBandwidth; +} + +// Return a 16-bit MOSI command (CALIBRATE or CLEAR) +int Rhd2000Registers::createRhd2000Command(Rhd2000CommandType commandType) +{ + switch (commandType) { + case Rhd2000CommandCalibrate: + return 0x5500; // 0101010100000000 + break; + case Rhd2000CommandCalClear: + return 0x6a00; // 0110101000000000 + break; + default: + cerr << "Error in Rhd2000Registers::createRhd2000Command: " << + "Only 'Calibrate' or 'Clear Calibration' commands take zero arguments." << endl; + return -1; + } +} + +// Return a 16-bit MOSI command (CONVERT or READ) +int Rhd2000Registers::createRhd2000Command(Rhd2000CommandType commandType, int arg1) +{ + switch (commandType) { + case Rhd2000CommandConvert: + if (arg1 < 0 || arg1 > 63) { + cerr << "Error in Rhd2000Registers::createRhd2000Command: " << + "Channel number out of range." << endl; + return -1; + } + return 0x0000 + (arg1 << 8); // 00cccccc0000000h; if the command is 'Convert', + // arg1 is the channel number + case Rhd2000CommandRegRead: + if (arg1 < 0 || arg1 > 63) { + cerr << "Error in Rhd2000Registers::createRhd2000Command: " << + "Register address out of range." << endl; + return -1; + } + return 0xc000 + (arg1 << 8); // 11rrrrrr00000000; if the command is 'Register Read', + // arg1 is the register address + break; + default: + cerr << "Error in Rhd2000Registers::createRhd2000Command: " << + "Only 'Convert' and 'Register Read' commands take one argument." << endl; + return -1; + } +} + +// Return a 16-bit MOSI command (WRITE) +int Rhd2000Registers::createRhd2000Command(Rhd2000CommandType commandType, int arg1, int arg2) +{ + switch (commandType) { + case Rhd2000CommandRegWrite: + if (arg1 < 0 || arg1 > 63) { + cerr << "Error in Rhd2000Registers::createRhd2000Command: " << + "Register address out of range." << endl; + return -1; + } + if (arg2 < 0 || arg2 > 255) { + cerr << "Error in Rhd2000Registers::createRhd2000Command: " << + "Register data out of range." << endl; + return -1; + } + return 0x8000 + (arg1 << 8) + arg2; // 10rrrrrrdddddddd; if the command is 'Register Write', + // arg1 is the register address and arg1 is the data + break; + default: + cerr << "Error in Rhd2000Registers::createRhd2000Command: " << + "Only 'Register Write' commands take two arguments." << endl; + return -1; + } +} + + +// Create a list of 60 commands to program most RAM registers on a RHD2000 chip, read those values +// back to confirm programming, read ROM registers, and (if calibrate == true) run ADC calibration. +// Returns the length of the command list. +int Rhd2000Registers::createCommandListRegisterConfig(vector<int> &commandList, bool calibrate) +{ + commandList.clear(); // if command list already exists, erase it and start a new one + + // Start with a few dummy commands in case chip is still powering up + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63)); + + // Program RAM registers + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 0, getRegisterValue( 0))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 1, getRegisterValue( 1))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 2, getRegisterValue( 2))); + // Don't program Register 3 (MUX Load, Temperature Sensor, and Auxiliary Digital Output); + // control temperature sensor in another command stream + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 4, getRegisterValue( 4))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 5, getRegisterValue( 5))); + // Don't program Register 6 (Impedance Check DAC) here; create DAC waveform in another command stream + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 7, getRegisterValue( 7))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 8, getRegisterValue( 8))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 9, getRegisterValue( 9))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 10, getRegisterValue(10))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 11, getRegisterValue(11))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 12, getRegisterValue(12))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 13, getRegisterValue(13))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 14, getRegisterValue(14))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 15, getRegisterValue(15))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 16, getRegisterValue(16))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 17, getRegisterValue(17))); + + // Read ROM registers + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 62)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 61)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 60)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 59)); + + // Read chip name from ROM + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 48)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 49)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 50)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 51)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 52)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 53)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 54)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 55)); + + // Read Intan name from ROM + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 40)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 41)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 42)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 43)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 44)); + + // Read back RAM registers to confirm programming + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 0)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 1)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 2)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 3)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 4)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 5)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 6)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 7)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 8)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 9)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 10)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 11)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 12)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 13)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 14)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 15)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 16)); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 17)); + + // Optionally, run ADC calibration (should only be run once after board is plugged in) + if (calibrate) { + commandList.push_back(createRhd2000Command(Rhd2000CommandCalibrate)); + } else { + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63)); + } + + // Added in Version 1.2: + // Program amplifier 31-63 power up/down registers in case a RHD2164 is connected + // Note: We don't read these registers back, since they are only 'visible' on MISO B. + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 18, getRegisterValue(18))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 19, getRegisterValue(19))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 20, getRegisterValue(20))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 21, getRegisterValue(21))); + + // End with a dummy command + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63)); + + + return commandList.size(); +} + +// Create a list of 60 commands to sample auxiliary ADC inputs, temperature sensor, and supply +// voltage sensor. One temperature reading (one sample of ResultA and one sample of ResultB) +// is taken during this 60-command sequence. One supply voltage sample is taken. Auxiliary +// ADC inputs are continuously sampled at 1/4 the amplifier sampling rate. +// +// Since this command list consists of writing to Register 3, it also sets the state of the +// auxiliary digital output. If the digital output value needs to be changed dynamically, +// then variations of this command list need to be generated for each state and programmed into +// different RAM banks, and the appropriate command list selected at the right time. +// +// Returns the length of the command list. +int Rhd2000Registers::createCommandListTempSensor(vector<int> &commandList) +{ + int i; + + commandList.clear(); // if command list already exists, erase it and start a new one + + tempEn = 1; + + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + tempS1 = tempEn; + tempS2 = 0; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + tempS1 = tempEn; + tempS2 = tempEn; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 49)); // sample Temperature Sensor + + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + tempS1 = 0; + tempS2 = tempEn; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 49)); // sample Temperature Sensor + + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + tempS1 = 0; + tempS2 = 0; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 48)); // sample Supply Voltage Sensor + + for (i = 0; i < 8; ++i) { + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 32)); // sample AuxIn1 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 33)); // sample AuxIn2 + commandList.push_back(createRhd2000Command(Rhd2000CommandConvert, 34)); // sample AuxIn3 + commandList.push_back(createRhd2000Command(Rhd2000CommandRegRead, 63)); // dummy command + } + + return commandList.size(); +} + +// Create a list of 60 commands to update Register 3 (controlling the auxiliary digital ouput +// pin) every sampling period. +// +// Since this command list consists of writing to Register 3, it also sets the state of the +// on-chip temperature sensor. The temperature sensor settings are therefore changed throughout +// this command list to coordinate with the 60-command list generated by createCommandListTempSensor(). +// +// Returns the length of the command list. +int Rhd2000Registers::createCommandListUpdateDigOut(vector<int> &commandList) +{ + int i; + + commandList.clear(); // if command list already exists, erase it and start a new one + + tempEn = 1; + + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + tempS1 = tempEn; + tempS2 = 0; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + tempS1 = tempEn; + tempS2 = tempEn; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + tempS1 = 0; + tempS2 = tempEn; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + tempS1 = 0; + tempS2 = 0; + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + + for (i = 0; i < 8; ++i) { + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 3, getRegisterValue(3))); + } + + return commandList.size(); +} + +// Create a list of up to 1024 commands to generate a sine wave of particular frequency (in Hz) and +// amplitude (in DAC steps, 0-128) using the on-chip impedance testing voltage DAC. If frequency is set to zero, +// a DC baseline waveform is created. +// Returns the length of the command list. +int Rhd2000Registers::createCommandListZcheckDac(vector<int> &commandList, double frequency, double amplitude) +{ + int i, period, value; + double t; + const double Pi = 2*acos(0.0); + + commandList.clear(); // if command list already exists, erase it and start a new one + + if (amplitude < 0.0 || amplitude > 128.0) { + cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: Amplitude out of range." << endl; + return -1; + } + if (frequency < 0.0) { + cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: Negative frequency not allowed." << endl; + return -1; + } else if (frequency > sampleRate / 4.0) { + cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: " << + "Frequency too high relative to sampling rate." << endl; + return -1; + } + if (frequency == 0.0) { + for (i = 0; i < MaxCommandLength; ++i) { + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 6, 128)); + } + } else { + period = (int) floor(sampleRate / frequency + 0.5); + if (period > MaxCommandLength) { + cerr << "Error in Rhd2000Registers::createCommandListZcheckDac: Frequency too low." << endl; + return -1; + } else { + t = 0.0; + for (i = 0; i < period; ++i) { + value = (int) floor(amplitude * sin(2 * Pi * frequency * t) + 128.0 + 0.5); + if (value < 0) { + value = 0; + } else if (value > 255) { + value = 255; + } + commandList.push_back(createRhd2000Command(Rhd2000CommandRegWrite, 6, value)); + t += 1.0 / sampleRate; + } + } + } + + return commandList.size(); +} diff --git a/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.h b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.h new file mode 100644 index 000000000..6c4744945 --- /dev/null +++ b/Source/Plugins/PCIeRhythm/rhythm-api/rhd2000registers.h @@ -0,0 +1,170 @@ +//---------------------------------------------------------------------------------- +// rhd2000registers.h +// +// Intan Technoloies RHD2000 Rhythm Interface API +// Rhd2000Registers Class Header File +// Version 1.4 (26 February 2014) +// +// Copyright (c) 2013-2014 Intan Technologies LLC +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the +// use of this software. +// +// Permission is granted to anyone to use this software for any applications that +// use Intan Technologies integrated circuits, and to alter it and redistribute it +// freely. +// +// See http://www.intantech.com for documentation and product information. +//---------------------------------------------------------------------------------- + +#ifndef RHD2000REGISTERS_H +#define RHD2000REGISTERS_H + +using namespace std; + +namespace PCIeRhythm { + class Rhd2000Registers + { + + public: + Rhd2000Registers(double sampleRate); + + void defineSampleRate(double newSampleRate); + + void setFastSettle(bool enabled); + + void setDigOutLow(); + void setDigOutHigh(); + void setDigOutHiZ(); + + void enableAux1(bool enabled); + void enableAux2(bool enabled); + void enableAux3(bool enabled); + + void enableDsp(bool enabled); + void disableDsp(); + double setDspCutoffFreq(double newDspCutoffFreq); + double getDspCutoffFreq() const; + + void enableZcheck(bool enabled); + void setZcheckDacPower(bool enabled); + + enum ZcheckCs { + ZcheckCs100fF, + ZcheckCs1pF, + ZcheckCs10pF + }; + + enum ZcheckPolarity { + ZcheckPositiveInput, + ZcheckNegativeInput + }; + + void setZcheckScale(ZcheckCs scale); + void setZcheckPolarity(ZcheckPolarity polarity); + int setZcheckChannel(int channel); + + void setAmpPowered(int channel, bool powered); + void powerUpAllAmps(); + void powerDownAllAmps(); + + int getRegisterValue(int reg) const; + + double setUpperBandwidth(double upperBandwidth); + double setLowerBandwidth(double lowerBandwidth); + + int createCommandListRegisterConfig(vector<int> &commandList, bool calibrate); + int createCommandListTempSensor(vector<int> &commandList); + int createCommandListUpdateDigOut(vector<int> &commandList); + int createCommandListZcheckDac(vector<int> &commandList, double frequency, double amplitude); + + enum Rhd2000CommandType { + Rhd2000CommandConvert, + Rhd2000CommandCalibrate, + Rhd2000CommandCalClear, + Rhd2000CommandRegWrite, + Rhd2000CommandRegRead + }; + + int createRhd2000Command(Rhd2000CommandType commandType); + int createRhd2000Command(Rhd2000CommandType commandType, int arg1); + int createRhd2000Command(Rhd2000CommandType commandType, int arg1, int arg2); + + private: + double sampleRate; + + // RHD2000 Register 0 variables + int adcReferenceBw; + int ampFastSettle; + int ampVrefEnable; + int adcComparatorBias; + int adcComparatorSelect; + + // RHD2000 Register 1 variables + int vddSenseEnable; + int adcBufferBias; + + // RHD2000 Register 2 variables + int muxBias; + + // RHD2000 Register 3 variables + int muxLoad; + int tempS1; + int tempS2; + int tempEn; + int digOutHiZ; + int digOut; + + // RHD2000 Register 4 variables + int weakMiso; + int twosComp; + int absMode; + int dspEn; + int dspCutoffFreq; + + // RHD2000 Register 5 variables + int zcheckDacPower; + int zcheckLoad; + int zcheckScale; + int zcheckConnAll; + int zcheckSelPol; + int zcheckEn; + + // RHD2000 Register 6 variables + //int zcheckDac; // handle Zcheck DAC waveform elsewhere + + // RHD2000 Register 7 variables + int zcheckSelect; + + // RHD2000 Register 8-13 variables + int offChipRH1; + int offChipRH2; + int offChipRL; + int adcAux1En; + int adcAux2En; + int adcAux3En; + int rH1Dac1; + int rH1Dac2; + int rH2Dac1; + int rH2Dac2; + int rLDac1; + int rLDac2; + int rLDac3; + + // RHD2000 Register 14-17 variables + vector<int> aPwr; + + double rH1FromUpperBandwidth(double upperBandwidth) const; + double rH2FromUpperBandwidth(double upperBandwidth) const; + double rLFromLowerBandwidth(double lowerBandwidth) const; + double upperBandwidthFromRH1(double rH1) const; + double upperBandwidthFromRH2(double rH2) const; + double lowerBandwidthFromRL(double rL) const; + + static const int MaxCommandLength = 1024; // size of on-FPGA auxiliary command RAM banks + + }; +}; + +#endif // RHD2000REGISTERS_H -- GitLab