Pump Library



This pump library defines a common pump control interface for a range of different pump types such as e.g. syringe pumps, dilutors, peristaltic pumps, or diaphragm pumps.

In general a pump consists of an actuator (like a linear drive for a syringe pump, a servo drive for a peristaltic pump or a simple DC/EC drive for a diaphragm pump) and a container for the transport of the fluid (like a syringe for a syringe pump/dilutor, a tube for a peristaltic pump or a pump head for the diaphragm pump). With some pumps like syringe pumps and peristaltic pumps the container is exchangeable by the application engineer or even by the user of the laboratory device. That means a user may mount different kind of syringes on a syringe pump or may use different tube sizes on a peristaltic pump. With other pumps (i.e. diaphragm pumps) the container and the actuator are fixed to each other and build up one single unit. The pump parameters (e.g. volume, flow or pressure) may be limited by the actuator and/or by the container.

Actuator limits

On a syringe pump the flow is limited by the speed of the actuator (the velocity of the linear drive) and the volume is limited by the length/maximum position of the linear drive. On a peristaltic pump or diaphragm pump the volume is not limited by the actuator but the maximum flow is limited by the actuator speed - the maximum rotation speed of the peristaltic pump drive or the speed of the DC/EC diaphragm pump drive.

Container limits

Container limits are only important for devices where it is possible to change the container. On every change of the container the calculation of the flow and volume values changes and therefore the parameters maximum volume and maximum flow may also change. That means each container has its own container limits. The maximum volume of a syringe pump is limited by the stroke of the syringe piston and the inner syringe diameter. The maximum flow is limited by the inner diameter of the syringe. The tube dimensions do not limit the maximum volume of a peristaltic pump but the maximum flow is limited by the inner diameter of the tube.


The way how you include the library functions in your own windows program, depends on the compiler and on the programming language you use. In order to access the pump API, you have to include the library labbCAN_Pump_API.dll to your programming environment. You have to copy this file to the working directory of your system or to the application directory that contains your application EXE file. Use the calling convention LCP_CALL for this library. This convention is managing how the parameters are put on the stack and who is responsible to clean the stack after the function execution.

The interface of the library, constant definitions and declarations of the library functions, is defined in the header file interface/labbCAN_Pump_API.h


Step 1 - Retrieve device handle

To work with a pump, you need a valid device handle. A device handle is simply an opaque pointer to the internal device object created by the labbCAN environment. So the first thing you need do to is obtaining a valid device handle.

You can get a valid pump handle with the functions LCP_LookupPumpByName() or LCP_GetPumpHandle():

dev_hdl PumpHandle;
Result = LCP_LookupPumpByName("Nemesys1_Pump", &PumpHandle);
dev_hdl PumpHandle2;
Result = LCP_GetPumpHandle(1, &PumpHandle2);
You can get the pump names from the device configuration files (see Device Configuration Files)

Step 2 - Enable devices

If you have valid device handle, you can start to bring your devices into a properly enabled and initialized state. To do this, you need to:

  1. clear all faults (LCP_ClearFault())
  2. enable all pump drives (LCP_Enable())

Step 3 - Initialize Position Sensing System

Each pump drive tracks the actual position value (volume value) by an internal position counter. If a positioning sensing system needs to get initialized depends on the type of used position sensor. Newer pumps, such as the Nemesys 4 pumps Nemesys S and Nemesys M have absolute encoders. These encoders keep their position value even when the pump is switched off. For such devices an initialization of the position sensing system is not required.

Other pumps, such as the older Nemesys Low Presssure Pumps or the Nemesys XL pumps use incremental encoders. If these pumps are switched off, the actual value of the position counter gets lost and is initialized to zero at next start. So right after power on, you don't know anything about the actual position of the pump drive units. For pumps like peristaltic pumps this does not matter but for pumps like syringe pumps this is very important because the position defines the actual fill level. For such devices an initialization of the position sensing system is required.

To find out, if an initialization is required, simply call the function LCP_IsPositionSensingInitialized(). This function always returns 1 for devices with an absolute encoder. For other devices, the returned value is 0 if the position value has not been initialized yet.

To properly initialize the internal position counter you either need to execute a reference move via LCP_SyringePumpCalibrate() or you need to restore a previously saved position counter value via LCP_RestoreDrivePosCnt(). Because a safe reference move is possible only if there is now syringe mounted on the device, the recommended way is using LCP_RestoreDrivePosCnt(). That means, right before you shut down your system, you need to query the actual position counter value of each pump via LCP_GetDrivePosCnt(). Then you need to store this value into a file. The next time you start up your devices, you load the saved position values, and write it back into the devices vial LCP_RestoreDrivePosCnt().

If you have a pump without absolute encoder and if you manually change the position of the pusher of this pump while it is switched off, the saved position counter value is not valid anymore and you need to execute a calibration move via LCP_SyringePumpCalibrate().

Step 4 - Ensure, that force monitoring is enabled

The newer Nemesys 4 devices (Nemesys S and Nemesys M) have internal force monitoring. The execution of dosing commands is only possible if the force monitoring is active. Force monitoring can be enabled / disabled via the function LCP_EnableForceMonitoring().

Initialization Example

The following example code snippet shows a valid valid pump initialization procedure:

1 //============================================================================
3 //============================================================================
7 //===========================================================================
9 //===========================================================================
10 static dev_hdl hPump1; ///< stores pump handle
12 //===========================================================================
13 // Pump initialisation
14 //===========================================================================
15 TErrCode InitPump(void)
16 {
17  TErrCode Result;
18  long Retval;
20  // Get a pump handle for the pump that is registered with the name
21  // Nemesys1_Pump
22  hPump1 = LCP_LookupPumpByName("Nemesys1_Pump");
23  if (hPump1 <= 0)
24  {
25  // handle error properly here
26  return -ERR_NOENT;
27  }
29  // Now we check if the pump is in a fault state. We need to clear the fault
30  // state first, before we can enable the pump drive
31  Retval = LCP_IsInFaultState(hPump1);
32  if (1 == Retval)
33  {
34  Result = LCP_ClearFault(hPump1);
35  if (Result != ERR_NOERR)
36  {
37  // handle error properly here
38  return Result;
39  }
40  }
42  // Now we enable the pump drive. Enabling the pump means, power
43  // will be applied to power stage of the pump drive units.
44  Result = LCP_Enable(hPump1);
45  if (Result != ERR_NOERR)
46  {
47  // handle error properly here
48  return Result;
49  }
51  // Now we check, if initializing the position sensing system is required.
52  // If it is required, then we restore the previously saved position
53  // counter value. If we do not have a saved position counter value
54  // then we need to execute a reference move via LCP_SyringePumpCalibrate().
55  Result = LCP_IsPositionSensingInitialized(hPump1);
56  if (!Result)
57  {
58  int SavedPositionCounterValue = 0;
59  //
60  // load saved position counter value from file here
61  // ...
63  // Now we restore the previously saved position counter value of the pumps
64  // drive units
65  Result = LCP_RestoreDrivePosCnt(hPump1, SavedPositionCounterValue);
66  if (Result != ERR_NOERR)
67  {
68  // handle error properly here
69  return Result;
70  }
71  }
73  // Now we setup volume and flow SI units - or we restore the values saved
74  // to file previously. We would like to use milliliters as volume unit and
75  // milliliters / second as flow unit
76  LCP_SetVolumeUnit(hPump1, MILLI, LITRES);
79  // Now we configure the syringe parameters of the syringe that is currently
80  // mounted on our syringe pump device - we use a syringe with 2 mm innder
81  // diameter and 60 mm piston stroke.
82  Result = LCP_SetSyringeParam(hPump1, 2, 60);
83  if (Result != ERR_NOERR)
84  {
85  // handle error properly here
86  return Result;
87  }
89  // now the pump is properly initialized and we can start dosing
90  return Result;
91 }
labbCAN Pump Application Programming Interface
#define ERR_NOENT
No such entity.
Definition: err_codes.h:104
#define PER_SECOND
Definition: labbCAN_MotionControl_API.h:214
long LCP_RestoreDrivePosCnt(dev_hdl hPump, long PosCntValue)
Restore internal hardware position counter value of pump drive.
int32_t TErrCode
Error code type.
Definition: err_codes.h:50
long LCP_LookupPumpByName(const char *pPumpName, dev_hdl *PumpHandle)
Lookup for a pump device by its name.
long long dev_hdl
generic device handle
Definition: labbCAN_Bus_API.h:172
long LCP_ClearFault(dev_hdl hPump)
Clear fault condition.
long LCP_SetSyringeParam(dev_hdl hPump, double InnerDiameter_mm, double MaxPistonStroke_mm)
Set syringe parameters.
long LCP_SetVolumeUnit(dev_hdl hPump, int Prefix, int VolumeUnit)
This function sets the default volume unit.
#define ERR_NOERR
No error.
Definition: err_codes.h:102
long LCP_SetFlowUnit(dev_hdl hPump, int Prefix, int VolumeUnit, int TimeUnit)
Sets the flow unit for a certain pump.
#define LITRES
Definition: labbCAN_Pump_API.h:67
#define MILLI
Definition: labbCAN_MotionControl_API.h:227
long LCP_IsPositionSensingInitialized(dev_hdl hPump)
Returns true, if the position sensing system is properly initialized.
long LCP_IsInFaultState(dev_hdl hPump)
Check if pump is in a fault state.
labbCAN Bus Application Programming Interface
long LCP_Enable(dev_hdl hPump)
Set axis into enabled state.


See the Pump API Initialisation module and the Pump Configuration module for a detailed reference of all pump initialization and configuration functions.


For almost all dosing tasks the functions from the Pump Control module and from the Pump Status module are required. Using the pump API you can choose from a number of different dosing modes:

  • LCP_Aspirate() for the aspiration of a certain volume with a certain flow
  • LCP_Dispense() for dispension of a certain volume
  • LCP_PumpVolume() for aspiration or dispension depending on the sign of the flow rate parameter (negativ = aspiration, positive = dispension)
  • LCP_SetFillLevel() aspirates or dispenses until a certain syringe fill level is reached
  • LCP_GenerateFlow() generates a constant flow - dosing continues until it gets stopped manually or until pusher reached one of its limits.
All dosing functions are non blocking. That means each function returns Immediately after the dosing process has been started and you need to poll the LCP_IsPumping() function to detect if dosing has been finished.

With the status functions you can always query the actual state of the pumps you are working with:

Dosing Example

The following example code snippet shows how to empty a syringe completely and then aspirate a certain volume with a certain flow rate.

1 //============================================================================
3 //============================================================================
7 //===========================================================================
9 //===========================================================================
10 static dev_hdl hPump1; ///< stores pump handle
12 //===========================================================================
13 // Pump dosage
14 //===========================================================================
15 TErrCode PumpDosageExample(void)
16 {
17  TErrCode Result;
18  long Retval;
20  // First we completely empty the syringe (fill level 0) with the maximum
21  // flow rate
22  double MaximumFlow;
23  Result = LCP_GetFlowRateMax(hNemesys1, &MaximumFlow);
24  Result = LCP_SetFillLevel(hPump1, 0, MaximumFlow);
25  if (Result != ERR_NOERR)
26  {
27  // handle error properly here
28  return Result;
29  }
32  Sleep(100); // Give pump some time to start dosage
34  // The loop continues until pump finished dosage
35  do
36  {
37  Retval = LCP_IsPumping(hPump1);
38  if (Result != ERR_NOERR)
39  {
40  // handle error properly here
41  return Result;
42  }
43  Sleep(50); // do not take all processor time with this loop
44  }
45  while (Retval > 0);
47  // now we aspirate 0.1 ml with a flow rate of 0.004 ml/s
48  Result = LCP_Aspirate(hPump1, 0.1, 0.004);
49  if (Result != ERR_NOERR)
50  {
51  // handle error properly here
52  return Result;
53  }
55  return ERR_NOERR;
56 }
long LCP_IsPumping(dev_hdl hPump)
Check if device is currently stopped or dosing.
labbCAN Pump Application Programming Interface
long LCP_Aspirate(dev_hdl hPump, double Volume, double Flow)
Aspirate a certain volume with a certain flow rate.
int32_t TErrCode
Error code type.
Definition: err_codes.h:50
long long dev_hdl
generic device handle
Definition: labbCAN_Bus_API.h:172
long LCP_SetFillLevel(dev_hdl hPump, double Level, double Flow)
Pumps fluid with the given flow rate until the requested fill level is reached.
long LCP_GetFlowRateMax(dev_hdl hPump, double *FlowRateMax)
Get maximum flow rate that is realizable with current dosing unit configuration.
#define ERR_NOERR
No error.
Definition: err_codes.h:102
labbCAN Bus Application Programming Interface

Dosing Reference

See the Pump Control module and the Pump Status module for a detailed reference of all pump dosing and status functions.

Force Monitoring

Overview of the force monitoring functionality

The newer Nemesys 4 pumps, such as Nemesys S and Nemesys M, have an internal force sensor for force monitoring and safety stop in case of too high forces (pressures). To find out if a pump supports force monitoring you can use the function LCP_HasForceMonitoring(). Only if this function returns 1 the force monitoring functions are supported. Each pump has an internal hardware specific maximum force. To find out the maximum force, simply call LCP_GetMaxDeviceForce(). If the force of the device raises above this maximum force, the pump drive is stopped immediately.

If you would like to know the SI unit that is used for all force values, call LCP_GetForceUnit()

It is possible to reduce the force limit below the maximum device force by setting a custom force limit if this is required by your application. You can do this by calling the function LCP_WriteForceLimit().

// Reduce the force limit to 0.2 kN
Result = LCP_WriteForceLimit(Pump1, 0.2);

If you would like to know the current force limit, simply call LCP_GetForceLimit(). The force limit defines the threshold for triggering the safety stop. If a safety stop is triggered, the pump drive is stopped and the safety stop input of the pump is set. To find out, if you are in a force overload situation, that means if the safety stop is active, you can read the state of the safety stop input via LCP_IsForceSafetyStopActive(). If this function returns 1 then you know, that the pump has been stopped because of an force overload.

How to resolve a force overload situation

If the safety stop is active, it is no longer possible to carry out any dosing with the pump. To resolve this force overload situation, you need to reduce the force below the force limit threshold. There are different ways to reduce the pressure:

  1. by using some kind of overpressure valve
  2. by waiting until the pressure is released
  3. by pulling back the syringe plunger (aspirating)

Pulling the syringe plunger is not possible as long as the safety stop input is active. To reduce the pressure by pulling the syringe plunger, you first need to disable the force monitoring by calling LCP_EnableForceMonitoring() function. This will clear the safety stop input.

Moving the syringe plunger when force monitoring is disabled is only possible in a force overload situation. If you disable force monitoring if the force is in the allowed range, then dosing is not possible.

If force monitoring is disabled, you can pull the syringe plunger by executing a flow command with a negative flow rate (aspirating). The following example shows how to do this:

double MaxFlow;
double Flow;
// Disable force monitoring
Result = LCP_EnableForceMonitoring(Pump1, 0);
// We use a small negative flow rate here, to pull the syringe plunger
Result = LCP_GetFlowRateMax(Pump1, &MaxFlow);
Flow = 0 - (MaxFlow / 100);
// Now we start the negative flow
Result = LCP_GenerateFlow(Pump1, Flow);

Now the syringe plunger will be pulled slowly out of the syringe and the pressure (force) will decrease. If the force falls below the force limit, then the safety stop input will be set and the pump will be stopped. To detect this event, you just need to monitor the safety stop input via LCP_IsForceSafetyStopActive().

SafetyStopActive = LCP_IsForceSafetyStopActive(Pump);
} while (!SafetyStopActive);

Now you can enable force monitoring again using LCP_EnableForceMonitoring() and the force overload situation is resolved.

The force monitoring functionality has a hysteresis. In case of a force overload situation you need to lower the force ca. 0.1 kN to clear the safety stop input. If this is not possible, you can lower the force less (for example 0.02 kN) and then call the function LCP_ClearForceSafetyStop() to clear the safety stop input.

Reading the force sensor

If you would like to monitor or log the force sensor value, you can read the current force using the function LCP_ReadForceSensor(). This will return the current force value in the SI unit returned by LCP_GetForceUnit().

Force Monitoring API Reference

See the Force Monitoring module for a detailed description and reference of all force monitoring functions.

Continuous Flow

Introduction into continuous flow functionality

A disadvantage of syringe pumps is that the maximum dosable volume is limited by the syringe. If the syringe is empty, the syringe must be refilled. During the refill cycle the dosing is interrupted. To solve this problem and enable uninterrupted, continuous dosing, the SDK offers Continuous Flow functionality.

To generate a continuous flow, two syringe pumps are connected by software to form a virtual continuous flow pump. The continuous flow algorithm then controls the alternating filling and emptying of the syringe, thus creating a continuous flow. The creation of the virtual continuous flow pump can be done during runtime via library functions or by declaring the continuous flow pump in the device configuration file.

Declare a continuous flow pump in device configuration file

The CETONI SDK requires a device configuration (a set of XML files) to control the supported devices. So the first step is to create a device configuration in the QmixElements software with at least 2 neMESYS syringe pumps. The device configuration folder now contains several XML files. Open the file nemesys.xml with an editor thats supports syntax highlighting (such as Notepad++).

Make a backup of your device configuration before you start editing files manually.

Now scroll to the end of the <Plugin><labbCAN><DeviceList> section to insert a new device declaration. The following example shows the declaration of a continuous flow pump at the end of the <DeviceList> section:

1 <Device Name="ContiFlowPump_1">
2  <Type>Lcl::CContiFlowPump</Type>
3  <Pump1 Device="neMESYS_Low_Pressure_1_Pump">
4  <Valve1 Device="neMESYS_Low_Pressure_1_Valve" AspiratingPos="1" DispensingPos="0" ClosedPos="-1"/>
5  </Pump1>
6  <Pump2 Device="neMESYS_Low_Pressure_2_Pump">
7  <Valve1 Device="neMESYS_Low_Pressure_2_Valve" AspiratingPos="1" DispensingPos="0" ClosedPos="-1"/>
8  </Pump2>
9 </Device>

The following things are declared in the example:

  • line 1: the device is declared and a unique name ContiFlowPump_1 is given
  • line 2: the device type is declared - this is always Lcl::CContiFlowPump for a continuous flow pump
  • line 3: the syringe pump neMESYS_Low_Pressure_1_Pump is assigned as the first pump channel. The syringe pump with the given name must exist in the device list above
  • line 4: the first valve neMESYS_Low_Pressure_1_Valve is assigned to the first syringe pump channel. For the valve the switching positions are configured for aspirating, dispensing and closed state. A value of -1 indicates that this position is not used and will not be switched.

In the following lines the second pump and the valve for the second pump are declared.

All devices (valves and pumps) that are assigned to the continuous flow pump must exist in the device list above.

If you now load this device configuration into the CETONI SDK, then you can easily access the continues flow pump from source code:

dev_hdl ContiFlowPump;
TErrCode Result = LCP_LookupPumpByName("ContiFlowPump_1", &ContiFlowPump);

Create a continuous flow pump in source code

As an alternative to creating the pump via the configuration file, the pump can also be created in source code at runtime. The first step is to obtain the device handles for all devices that should be combined into a continuous flow pump. Any error checking code has been removed to keep the example simple.

TErrCode Result;
dev_hdl SyringePump1;
dev_hdl SyringePump2;
dev_hdl Valve1;
dev_hdl Valve2;
Result = LCP_LookupPumpByName("neMESYS_Low_Pressure_1_Pump", &SyringePump1);
Result = LCP_LookupPumpByName("neMESYS_Low_Pressure_2_Pump", &SyringePump2);
Result = LCV_LookupValveByName("neMESYS_Low_Pressure_1_Valve", &Valve1);
Result = LCV_LookupValveByName("neMESYS_Low_Pressure_2_Valve", &Valve2);

If you have all required device handles, you can start to create the pump device:

dev_hdl ContiFlowPump;
Result = LCP_CreateContiFlowPump(SyringePump1, SyringePump2, &ContiFlowPump);
Result = LCP_ConfigureContiFlowValve(ContiFlowPump, 0, 0, Valve1, 1, 0, -1);
Result = LCP_ConfigureContiFlowValve(ContiFlowPump, 1, 0, Valve2, 1, 0, -1);

First the continuoues flow pump is created by assigning the two syringe pumps. The device handle is returned in the ContiFlowPump variable. Then the first valve for the first pump channel is configured and the the first valve for the second pump channel. After these steps, the continuous flow pump has the same configuration like the pump created via the device configuration file above.

Parameterize the continuous flow

If you have a valid continuous flow pump handle, you can start to parameterize the continuous flow. This is the same procedure like you know if from the continuous flow wizard in the QmixElements software. At the moment, the continus flow pump supports only cross flow switching. This means, the cross flow algorithm provides a soft transition and a constant flow rate when switching from one dosing unit to the other. This mode is most suitable if your application uses a low system pressure and you don’t have pressure sensors.

The first thing you need to configure is the switching mode for the continuous flow. At the moment only the cross flow is supported but pressure controlled switching will come soon.

Then you need to configure the maximum refill flow rate that is allowed in your application. If you would like to know the maximum possible refill flow, then you can query it like this:

double MaxRefillFlow;
Result = LCB_GetDeviceProperty(ContiFlowPump, PropertyMaxRefillFlow, &MaxRefillFlow);

Then you can limit the maximum flow rate like this:

Result = LCB_SetDeviceProperty(ContiFlowPump, PropertyRefillFlow, MaxRefillFlow / 2.0);

The next parameter that you can configure is the cross flow duration. This is the time to cross-fade the flows of both syringe pumps. The smaller this value, the steeper the flow rate curve and the less time is required for the cross-flow operation to be completed. A value of 1 second is a good initial default value. If you do not want to cross fade, then use a value of 0.

Result = LCB_SetDeviceProperty(ContiFlowPump, PropertyCrossFlowDuration_s, 1.0);

An optional parameter that you can configure is the overlap duration. This is the period of time by which the flow curves of both pumps overlap. It is a simple way of compensating for pressure drops during switching, because the second pump starts earlier to pressurize. The larger the value, the longer both pumps keep dosing simultaneously

Result = LCB_SetDeviceProperty(ContiFlowPump, PropertyOverlapDuration_s, 0.0);

If you have configured all parameters, you can no query the maximum continuous flow rate. Because of the times for switching, cross flow and overlap, the maximum flow rate of the continuous flow pump is lower than the maximum flow rate of the single syringe pumps.

double MaxContiFlowRate;
Result = LCP_GetFlowRateMax(ContiFlowPump, &MaxContiFlowRate);

Continuous flow initialization

To start the continuous flow, the pump needs to be initialized properly. This means that at least one of the two syringe pumps should contain enough liquid for dosing so that the other pump can be filled during dosing.

Result = LCP_ClearFault(ContiFlowPump);
Result = LCP_Enable(ContiFlowPump);
// Reference move is only required if the syringe pumps are not referenced yet
dev_hdl SyringePump0;
Result = LCP_GetContiFlowSyringePump(ContiFlowPump, 0, &SyringePump0);
Result = LCP_SyringePumpCalibrate(SyringePump0);
// Reference move is only required if the syringe pumps are not referenced yet
dev_hdl SyringePump1;
Result = LCP_GetContiFlowSyringePump(ContiFlowPump, 1, &SyringePump1);
Result = LCP_SyringePumpCalibrate(SyringePump1);
// you need to wait until calibration of both syringe pumps has finished
// before you start the initialization
Result = LCP_InitializeContiFlow(ContiFlowPump2);

The first step is to clear all potential errors of the pump. Then you need to bring the pump into enable state - that means, both syringe pumps should be enabled. If the positioning system of both syringe pumps has not been initialized, then you need to excute a reference move via LCP_SyringePumpCalibrate function.

The last step will start the initialization procedure that will fill one syringe with the configured refill flow rate. See capi_nemesys_test.cpp for more details.

Continuous Flow Usage

If you have properly configured and initialized your continuous flow pump, you can use it like any other pump. That means, you can use the normal pump control and pump status functions.

double MaxContiFlowRate;
Result = LCP_GetFlowRateMax(ContiFlowPump1, &MaxContiFlowRate);
Result = LCP_GenerateFlow(ContiFlowPump1, MaxContiFlowRate);
The continuous flow mode is meant for dispensing volumes or generating flows and works only in one direction. That means using negative flow rates or negative volumes for aspiration is not possible.

Programming Interface - API

See the labbCAN Pump API module for a detailed reference of the pump library application programming interface