diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index a8fb0b502f..0ca425d7b3 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -130,9 +130,14 @@ Constant FREE_MEMORY_LOWER_LIMIT = 0.75 /// @name Pressure Control constants ///@{ -/// Max and min pressure regulator pressure in psi +/// Max, min and atmospheric pressure regulator pressure in psi Constant MAX_REGULATOR_PRESSURE = 9.9 Constant MIN_REGULATOR_PRESSURE = -9.9 +Constant ATMOSPHERIC_PRESSURE = 0 +/// valve for pressure routing +Constant ACCESS_ATM = 0 // Access constants are used to set TTL valve configuration +Constant ACCESS_REGULATOR = 1 +Constant ACCESS_USER = 2 ///@} /// The indizies correspond to the values from @ref XopChannelConstants diff --git a/Packages/MIES/MIES_DAEphys.ipf b/Packages/MIES/MIES_DAEphys.ipf index 252af1c054..a254ff67af 100644 --- a/Packages/MIES/MIES_DAEphys.ipf +++ b/Packages/MIES/MIES_DAEphys.ipf @@ -1922,7 +1922,7 @@ Function DAP_SetVarProc_CAA(STRUCT WMSetVariableAction &sva) : SetVariableContro // --- updates pressure during manual pressure mode when TP is not running --- variable hs = DAG_GetNumericalValue(device, "slider_DataAcq_ActiveHeadstage") - if(P_GetPressureMode(device, hs) == PRESSURE_METHOD_MANUAL) + if(P_GetPressureMethod(device, hs) == PRESSURE_METHOD_MANUAL) P_RunP_ControlIfTPOFF(device) endif diff --git a/Packages/MIES/MIES_ForeignFunctionInterface.ipf b/Packages/MIES/MIES_ForeignFunctionInterface.ipf index 193981a926..8b7b81acbc 100644 --- a/Packages/MIES/MIES_ForeignFunctionInterface.ipf +++ b/Packages/MIES/MIES_ForeignFunctionInterface.ipf @@ -1,6 +1,7 @@ #pragma TextEncoding = "UTF-8" -#pragma rtGlobals = 3 // Use modern global access method and strict wave access. +#pragma rtGlobals = 3 // Use modern global access method and strict wave access. #pragma rtFunctionErrors = 1 +#pragma DefaultTab = {3, 20, 4} // Set default tab width in Igor Pro 9 and later #ifdef AUTOMATED_TESTING #pragma ModuleName = MIES_FFI @@ -317,9 +318,259 @@ End Function FFI_TestPulseMDSingleResult(string device, variable action) variable ret - string errorMsg + string errorMsg [ret, errorMsg] = FFI_TestPulseMD(device, action) return ret End + +// for external callers to set manual pressure +Function DoPressureManual(string device, variable headstage, [variable manualOnOff, variable targetPressure, variable userAccessOnOff]) + + // 0) Select the headstage + + if(DAG_GetNumericalValue(device, "slider_DataAcq_ActiveHeadstage") != headStage) + PGC_SetAndActivateControl(device, "slider_DataAcq_ActiveHeadstage", val = headstage) + endif + + // 1) Set the requested pressure value on the GUI control + PGC_SetAndActivateControl(device, "setvar_DataAcq_SSPressure", val = targetPressure) + + // 2) Check the current pressure mode + variable currentMode = P_GetPressureMethod(device, headstage) + + // 3) If we want manual mode ON... + if(manualOnOff == 1) + // ...and we are NOT in manual mode yet, switch to manual + if(currentMode != PRESSURE_METHOD_MANUAL) + P_SetManual(device, "button_DataAcq_SSSetPressureMan") + endif + else + // If we want manual mode OFF... + // ...and we ARE currently in manual mode, switch to atmospheric (or the "off" state) + if(currentMode == PRESSURE_METHOD_MANUAL) + P_SetManual(device, "button_DataAcq_SSSetPressureMan") + endif + endif + +End + +// ----------------------------- +// FFI: GetPressureWithOptionToSetSourceAndPressure +// ----------------------------- +/// @brief FFI entry: set/rout pressure per 'requestedSource' and/or set regulator setpoint. +/// @param device MIES device name +/// @param headstage target headstage index +/// @param requestedSource "atmosphere" | "regulator" | "user" | "default" | "" +/// @param requestedPressure regulator setpoint (psi; <9.9, >-9.9) | NaN +/// @return regulatorPressure, pipettePressure + +Function [string source, variable regulatorPressure] FFI_GetWithOptionToSetPressure(string device, variable headstage, string requestedSource, variable requestedPressure) + + // ---- Safety & validation ---- + DAP_AbortIfUnlocked(device) + ASSERT(IsValidHeadstage(headstage), "Invalid headstage (0–7)") + + // Normalize inputs + string src = TrimString(requestedSource) + // hoops to jump through because xop toolkit used for zeroMQ XOP does not support optional parameters + variable hasSetpoint = (numtype(requestedPressure) == 0) // finite number? + if(numtype(requestedPressure) == 1) // +/-INF -> treat as "no change" + hasSetpoint = 0 + endif + + // If a pressure pulse is still running, wait it out. Max pressure pulse time is 300 ms. + FFI_WaitForIdle(device, headstage) + + // ---- Apply according to contract ---- + // strswitch is case-insensitive in Igor + strswitch(src) + + case "atmosphere": + // Route to atmosphere + if(hasSetpoint) + P_SetPressureMode(device, headstage, PRESSURE_METHOD_ATM) + PGC_SetAndActivateControl(Device, "setvar_DataAcq_SSPressure", val = requestedPressure) + else + P_SetPressureMode(device, headstage, PRESSURE_METHOD_ATM) + endif + P_PressureControl(device) + PGC_SetAndActivateControl(device, "check_DataAcq_Pressure_User", val = CHECKBOX_UNSELECTED) + break + + case "regulator": + // Enter/keep manual; set setpoint if provided + if(hasSetpoint) + P_SetPressureMode(device, headstage, PRESSURE_METHOD_MANUAL, pressure = requestedPressure) + else + P_SetPressureMode(device, headstage, PRESSURE_METHOD_MANUAL) + endif + P_PressureControl(device) + PGC_SetAndActivateControl(device, "check_DataAcq_Pressure_User", val = CHECKBOX_UNSELECTED) + + break + + case "user": + // Optionally update regulator setpoint first (even though pipette will route to user) + if(hasSetpoint) + P_SetPressureMode(device, headstage, PRESSURE_METHOD_MANUAL, pressure = requestedPressure) + P_PressureControl(device) + endif + // Now route valves to the user line + PGC_SetAndActivateControl(device, "check_DataAcq_Pressure_User", val = CHECKBOX_SELECTED) + // Ensure valves reflect current mode/access explicitly + P_SetPressureValves(device, headstage, \ + P_GetUserAccess(device, headstage, P_GetPressureMethod(device, headstage))) + break + + case "default": // fallthrough + case "": + // No routing change; set manual setpoint only if provided + if(hasSetpoint) + PGC_SetAndActivateControl(Device, "setvar_DataAcq_SSPressure", val = requestedPressure) + endif + break + + default: + // Unknown token -> treat like "default" (no routing change) + if(hasSetpoint) + PGC_SetAndActivateControl(Device, "setvar_DataAcq_SSPressure", val = requestedPressure) + endif + break + endswitch + + // ---- Readback ---- + + [source, regulatorPressure] = ReadPressureSourceAndPressure(device, headstage) + return [source, regulatorPressure] +End + +Function [string source, variable pressure] ReadPressureSourceAndPressure(string device, variable headstage) + + WAVE PD = P_GetPressureDataWaveRef(device) + pressure = PD[headstage][%LastPressureCommand] + + variable mode = P_GetPressureMethod(device, headstage) + variable access = P_GetUserAccess(device, headstage, mode) + + if(mode == PRESSURE_METHOD_ATM) // need to think about how to handle unimplemented manual pressure command versus implemented pressure command + source = "atmosphere" + elseif(access == ACCESS_USER) + source = "user" + else + source = "regulator" + endif + + return [source, pressure] + +End + +// Max pressure pulse is 300 ms. Add a little slack for routing/GUI churn. +static Constant kPressurePulseMaxMS = 300 +static Constant kPressureWaitSlackMS = 150 + +/// Return 1 if idle, 0 if we timed out still busy. +Function FFI_WaitForIdle(string device, variable headstage) + + WAVE PD = P_GetPressureDataWaveRef(device) + + // Fast path: already idle + if(!PD[headstage][%OngoingPressurePulse]) + return 1 + endif + + variable t0 = stopmstimer(-2) + // Wait until the pulse finishes or timeout elapses + do + if(!PD[headstage][%OngoingPressurePulse]) + return 1 + endif + + // Timeout check (treating stopmstimer(-2) deltas as milliseconds) + if((stopmstimer(-2) - t0) > (kPressurePulseMaxMS + kPressureWaitSlackMS)) + // one last check before giving up + return !PD[headstage][%OngoingPressurePulse] + endif + + DoUpdate // yield to background tasks/UI + while(1) +End + +Function SetPressureToBaseline() + + string source + variable pressure + [source, pressure] = FFI_GetWithOptionToSetPressure("ITC1600_Dev_0", 0, "atmosphere", 0) +End + +Function readoutPressureSourceAndPressure() // should readout source and pressure without making changes to pressure settings + + SetPressureToBaseline() + string sourceIn, sourceOut + variable pressureIn, PressureOut + WAVE PD = P_GetPressureDataWaveRef("ITC1600_Dev_0") + [SourceIn, pressureIn] = ReadPressureSourceAndPressure("ITC1600_Dev_0", 0) + pressureIn = PD[0][%ManSSPressure] + [SourceOut, pressureOut] = FFI_GetWithOptionToSetPressure("ITC1600_Dev_0", 0, "default", NaN) + pressureOut = PD[0][%ManSSPressure] + assert(!cmpstr(SourceIn, SourceOut), "changed source when it shouldn't have") + assert(pressureIn == pressureOut, " changed pressure when it shouldn't have") +End + +Function SetRegulatorPressure() // should set the pressure of the next manual pressure command to 2 psi. Should not turn on manual pressure + + SetPressureToBaseline() + string sourceIn, sourceOut + variable pressureIn, PressureOut, NextRegulatorPressureCommand + WAVE PD = P_GetPressureDataWaveRef("ITC1600_Dev_0") + [SourceIn, pressureIn] = ReadPressureSourceAndPressure("ITC1600_Dev_0", 0) + [SourceOut, pressureOut] = FFI_GetWithOptionToSetPressure("ITC1600_Dev_0", 0, "default", 2) + NextRegulatorPressureCommand = PD[0][%ManSSPressure] + assert(!cmpstr(SourceIn, SourceOut), "changed source when it shouldn't have") + assert(pressureOut == 0, "set output pressure when it shouldn't have") + assert(NextRegulatorPressureCommand == 2, "did not set the next manual/regulator pressure command to 2 psi") + +End + +Function SetRegulatorPressureAndSetREgulatorSource() // should set the pressure of the manual pressure command to 3 psi and set the source to regulator. Should not turn on manual pressure + + SetPressureToBaseline() + string sourceIn, sourceOut + variable pressureIn, PressureOut + [SourceIn, pressureIn] = ReadPressureSourceAndPressure("ITC1600_Dev_0", 0) + [SourceOut, pressureOut] = FFI_GetWithOptionToSetPressure("ITC1600_Dev_0", 0, "regulator", 3) + assert(!cmpstr("regulator", SourceOut), "did not change the source to regulator") + assert(pressureOut == 3, "did not set pressure to 3 psi") +End + +Function SetSourceToRegulator() // should set the source to regulator/manual without changing the regulator pressure. + + SetPressureToBaseline() + string sourceIn, sourceOut + variable pressureIn, PressureOut + [SourceIn, pressureIn] = ReadPressureSourceAndPressure("ITC1600_Dev_0", 0) + [SourceOut, pressureOut] = FFI_GetWithOptionToSetPressure("ITC1600_Dev_0", 0, "regulator", NaN) + assert(!cmpstr("regulator", SourceOut), "did not change the source to regulator") + assert(pressureIn == pressureOut, "changed the pressure!") +End + +Function SetSourceToUserSetPressureToNeg2() // should set the source to user and set the next manual pressure command to -2psi + + SetPressureToBaseline() + string sourceIn, sourceOut + variable pressureIn, PressureOut + [SourceIn, pressureIn] = ReadPressureSourceAndPressure("ITC1600_Dev_0", 0) + [SourceOut, pressureOut] = FFI_GetWithOptionToSetPressure("ITC1600_Dev_0", 0, "user", -2) + assert(!cmpstr("user", SourceOut), "did not change the source to user") + assert(-2 == pressureOut, "changed the pressure!") +End + +Function RunTests() + + readoutPressureSourceAndPressure() + SetRegulatorPressure() + SetRegulatorPressureAndSetREgulatorSource() + setSourceToRegulator() + SetSourceToUserSetPressureToNeg2() +End diff --git a/Packages/MIES/MIES_PressureControl.ipf b/Packages/MIES/MIES_PressureControl.ipf index c231c9d75c..e5093d8fbb 100644 --- a/Packages/MIES/MIES_PressureControl.ipf +++ b/Packages/MIES/MIES_PressureControl.ipf @@ -38,16 +38,12 @@ static Constant GIGA_SEAL = 1000 static Constant PRESSURE_OFFSET = 5 static Constant MIN_NEG_PRESSURE_PULSE = -2 static Constant MAX_POS_PRESSURE_PULSE = 0.1 -static Constant ATMOSPHERIC_PRESSURE = 0 static Constant PRESSURE_CHANGE = 1 static Constant P_NEGATIVE_PULSE = 0x0 static Constant P_POSITIVE_PULSE = 0x1 static Constant P_MANUAL_PULSE = 0x2 static Constant SEAL_POTENTIAL = -70 // mV static Constant SEAL_RESISTANCE_THRESHOLD = 100 // MΩ -static Constant ACCESS_ATM = 0 // Access constants are used to set TTL valve configuration -static Constant ACCESS_REGULATOR = 1 -static Constant ACCESS_USER = 2 ///@} /// @brief Filled by P_GetPressureForDA() @@ -209,7 +205,7 @@ static Function P_AddSealedEntryToTPStorage(string device, variable headstage) End /// @brief Sets the pressure to atmospheric -static Function P_MethodAtmospheric(string device, variable headstage) +Function P_MethodAtmospheric(string device, variable headstage) WAVE PressureDataWv = P_GetPressureDataWaveRef(device) P_SetPressureValves(device, headStage, P_GetUserAccess(device, headStage, PRESSURE_METHOD_ATM)) @@ -2026,7 +2022,7 @@ static Function/WAVE P_DecToBinary(variable dec) End /// @brief Manual pressure control -static Function P_ManSetPressure(string device, variable headStage, variable manPressureAll) +Function P_ManSetPressure(string device, variable headStage, variable manPressureAll) WAVE PressureDataWv = P_GetPressureDataWaveRef(device) @@ -2071,7 +2067,7 @@ End /// @brief Gets the pressure mode for a headstage /// -Function P_GetPressureMode(string device, variable headStage) +Function P_GetPressureMethod(string device, variable headStage) return P_GetPressureDataWaveRef(device)[headStage][%Approach_Seal_BrkIn_Clear] End @@ -2089,7 +2085,7 @@ Function P_SetPressureMode(string device, variable headStage, variable pressureM ASSERT(pressureMode >= PRESSURE_METHOD_ATM && pressureMode <= PRESSURE_METHOD_MANUAL, "Select a pressure mode between -1 and 4") WAVE PressureDataWv = P_GetPressureDataWaveRef(device) - variable activePressureMode = P_GetPressureMode(device, headStage) + variable activePressureMode = P_GetPressureMethod(device, headStage) variable UserSelectedHS = PressureDataWv[headStage][%UserSelectedHeadStage] if(!paramIsDefault(pressure) && pressureMode == PRESSURE_METHOD_MANUAL) @@ -2187,7 +2183,7 @@ Function ButtonProc_Clear(STRUCT WMButtonAction &ba) : ButtonControl End /// @brief Handles the TP depency of the Manual pressure application -static Function P_SetManual(string device, string cntrlName) +Function P_SetManual(string device, string cntrlName) P_UpdatePressureMode(device, PRESSURE_METHOD_MANUAL, cntrlName, 1) P_RunP_ControlIfTPOFF(device) @@ -2514,31 +2510,3 @@ Function/S P_PressureMethodToString(variable method) FATAL_ERROR("Unknown pressure method: " + num2str(method)) endswitch End - -// for external callers to set manual pressure -Function DoPressureManual(string device, variable headstage, variable manualOnOff, variable targetPressure) - - // 0) Select the headstage - PGC_SetAndActivateControl(device, "slider_DataAcq_ActiveHeadstage", val = headstage) - - // 1) Set the requested pressure value on the GUI control - PGC_SetAndActivateControl(device, "setvar_DataAcq_SSPressure", val = targetPressure) - - // 2) Check the current pressure mode - variable currentMode = P_GetPressureMode(device, headstage) - - // 3) If we want manual mode ON... - if(manualOnOff == 1) - // ...and we are NOT in manual mode yet, switch to manual - if(currentMode != PRESSURE_METHOD_MANUAL) - P_SetManual(device, "button_DataAcq_SSSetPressureMan") - endif - else - // If we want manual mode OFF... - // ...and we ARE currently in manual mode, switch to atmospheric (or the "off" state) - if(currentMode == PRESSURE_METHOD_MANUAL) - P_SetManual(device, "button_DataAcq_SSSetPressureMan") - endif - endif - -End