How to Control a LabV Pump Using the MODBUS Protocol ?

How to Control a LabV Pump Using the MODBUS Protocol ?

The LabV pump series from Shenchen Baoding is an advanced series of flow measurement peristaltic pumps featuring a 4.3-inch color touch screen for intuitive control. It displays flow data, settings, and system information simultaneously, with animations to show the working state. Equipped with intelligent calibration, online micro-adjustment, and three measurement modes—fixed volume, fixed time and volume, and timer start/stop—it’s highly versatile. Compatible with various pump heads and offering multiple external control options, the LabV series is ideal for laboratory use, equipment integration, and industrial production.

In this blog post, we will explore how to control the LabV pumps using the MODBUS RTU protocol via a common programming platform: Python.

It’s worth noting that while there may be slight differences in registers set up and commands among various Lab pump series (such as LabF, LabN, LabK, etc.), they all share the same MODBUS RTU communication protocol. Therefore, the insights provided in this article are relevant and applicable to users of any Shenchen Lab pump series.

TABLE OF CONTENTS

Why Choose a LabV Series Peristaltic Pump?

The LabV Peristaltic Pump stands out as a top choice for fluid handling needs in laboratories and industrial settings alike. Boasting a new servo motor for enhanced accuracy and durability, coupled with a 4.3″ LCD touchscreen interface, this pump offers intuitive control for precise dispensing and fixed volume modes. Crafted from laboratory-grade ABS, it ensures both ease of use and robust performance.

With flow rates spanning from 0.0053 µL/min to 775 mL/min, and the flexibility to accommodate up to 8 channels, it caters to a wide range of applications. Additionally, its industrial-grade construction, dynamic display, and intelligent calibration functionality make it a reliable asset for various fluid transfer tasks.

Whether for research, equipment support, or industrial production, the LabV series of peristaltic pumps stands as a very good solution for fluid handling challenges.

What do you Need to Communicate with a LabV Pump?

To communicate with a LabV pump using the MODBUS RTU protocol, you will need the following:

  • A LabV pump (a LabV1-III Intelligent Flow Rate Peristaltic Pump will be utilized herein) with a pump head and a tubing of your choice
  • A serial cable that connects to your computer or device
  • An RS485 or an RS232 wiring onto the external control port on the rear of the pump
  • A programming platform (Python in the case herein)
  • A basic understanding of the MODBUS RTU communication protocol detailed below 👇.

How to Set Up the Communication Port ?

Before you can start sending and receiving data from the pump, you need to set up the communication port correctly. Here are the steps to follow:

  • Connect the cable for the RS232 or RS485 communication to the external control port on the rear of the pump
  • Turn on the pump
  • In the communication setting interface on the pump screen, choose RS232 or RS485 as your communication interface (details of the RS232 and RS485 wiring are presented in the tables below)
  • No matter if you choose RS232 or RS485, the communication protocol is standard MODBUS RTU
  • Set the baud rate
  • Click on the Slave No. button to set the address of the peristaltic pump (range: 1-32)
  • Select communication enable ON
  • Return to the main interface as the pump only receives communication control when in the main interface, it is invalid for the communication control in other settings interfaces.
A screenshot of the communication setting interface of the LabV1-III pump
Table Example
RS232 Wiring Details
Terminal Description
GND Communication ground terminal.
TXD Master sending, peristaltic pump receive signal terminal.
RXD Peristaltic pump sending, master receive signal terminal.
RS485 Wiring Details
Terminal Description
GD1 RS485 signal ground.
A+ Connect RS485 A+ terminal.
B- Connect RS485 B- terminal.

The LabV Series MODBUS RTU Communication Protocol

The LabV Peristaltic Pump series offers a comprehensive command set for precise control and monitoring via the MODBUS RTU protocol. This command set encompasses various directives categorized to facilitate manipulation of the pump’s functionalities, covering actions such as motor speed settings, flow rate configurations, pump head selections, and calibration routines. Each command, identified by unique parameters, ensures precise control over the pump’s operation.

Detailed explanations and further information regarding the MODBUS RTU protocol for the control of the LabV pumps will be provided below and can also be found in the communication protocol’s documentation.

MODBUS RTU Standard Communication Format

The message format is the one described in the table below. 

FieldSizeDescription
Slave address1 ByteAddress of the pump. The No. range is 1-32 and 0 is used for broadcast.
Function code1 ByteInstruction for the slave device.
03H: Read holding registers
06H: Write single register
10H: Write multiple registers
02H: Read discrete inputs (Read bits of data)
05H: Write single bit to register
Data area0 – 252 BytesData to be transmitted (variable length)
CRC Check2 BytesCyclic Redundancy Check for error detection
CRC low (1 Byte): Lower byte of CRC
CRC high (1 Byte): Higher byte of CRC

CRC Check with CRC-16 in Python

CRC (Cyclic Redundancy Check) is a vital technique for detecting errors in data transmission. In the following, we’ll demonstrate how to implement CRC-16, the one used in the American binary synchronous system, using Python.

In CRC-16, the error-detection algorithm employs a polynomial G(X) = X16 + X15 + X2 + 1 to generate the checksum. This polynomial defines the mathematical operations performed on the bits of the message during the CRC calculation process. An overview of the algorithm is detailed below:

  • Initialize a 16-bit register with the hexadecimal value FFFF (all ones), referred to as the CRC register.
  • XOR the first 8 bytes of the message with the low byte of the CRC register. Store the result back in the CRC register.
  • Shift the CRC register one bit to the right, with the most significant bit (MSB) set to zero.
  • Check the least significant bit (LSB) of the CRC register:
    • If LSB is 0, repeat step 3 (shift operation).
    • If LSB is 1, XOR the CRC register with the polynomial value 0xA001 (1010 0000 0000 0001).
  • Repeat steps 3-4, for a total of 8 shifts, to process each byte.
  • Repeat steps 2-5 for each byte in the message until all bytes have been processed.
  • The final content of the CRC register is the CRC value.

🚨 In the message, the high and low bytes of the CRC values must be exchanged.

Communication Settings

Each byte of data has a structure consisting of 1 start bit, 8 data bits, 1 parity bit, and 1 stop bit. Bits are sent in order from the least significant bit (LSB) to the most significant bit (MSB). The possible communication baud rates include 1200, 2400, 4800, and 9600 bits/s. 

For integers (2 bytes), the data is arranged with the second byte first followed by the first byte. For example, the hexadecimal value 2378H is sent as 23H 78H. For long integers and floats (4 bytes), the data is arranged with the fourth byte first, followed by the third, second, and first bytes.

MODBUS Message RTU Frame Format

The MODBUS message RTU frame format is the one described in the table below. 

FieldSizeDescription
Start≥ 3.5 charactersIndicates the start of transmission
Address8 BitsAddress of the slave device
Function Code8 BitsInstruction for the slave device
DataN x 8 BitsData to be transmitted (variable length, N bytes)
CRC Check Code16 BitsCyclic Redundancy Check for error detection
End≥ 3.5 charactersIndicates the end of transmission

🚨 Ensure that the message frame is transmitted continuously without idle spaces between characters greater than 1.5 character times. Incomplete message frames, indicated by excessive idle space, should be discarded by the receiving node.

Abnormal Reaction

When a host sends a request for data and the slave receives abnormal data, it should trigger an abnormal reaction. If the address code sent from the host is incorrect, meaning it does not correspond to any address code among the slaves, or if the data received by the slave fails the CRC check, no abnormal code is returned. In such cases, the host should initiate a superior reaction process.

In the function code area, the abnormal reaction function code is defined as the normal reaction function code plus 80H.

In the data area, the abnormal code to be returned is defined as in the table below.

Code Name Meaning
01H Illegal function code The function code received by the peristaltic pump is not permitted, except for 03H, 06H, or 10H.
02H Illegal data address The register address received by the peristaltic pump is not allowed.
03H Illegal data value Written data does not meet the operating range expected by the peristaltic pump.
06H Slave (peristaltic pump) busy The peristaltic pump is currently busy and unable to complete the command due to a conflicting state.

 🚨The peristaltic pump only accepts MODBUS commands through the Main Interface; other interfaces do not receive messages.

Holding Register Address and Content

Below are the tables detailing basic and calibration parameters setting respectively.

Basic Parameters Setting

Address (Decimal)NameRangeData Type
1000Pump HeadRelative to Chart 1unsigned short int (2 Bytes)
1001Tubing SizeRelative to Chart 1unsigned short int (2 Bytes)
1002Motor Speed0.1-600 rpmfloat (4 Bytes)
1004Flow Rate0.1-99999 mLfloat (4 Bytes)
1007Back Suction Angle0-360°unsigned short int (2 Bytes)
1008Start/Stop Control1: Start, 0: Stopunsigned short int (2 Bytes)
1009Direction Control1: Clockwise, 0: Counter-clockwiseunsigned short int (2 Bytes)
1010Full Speed Running1: Start full speed, 0: Stop full speedunsigned short int (2 Bytes)
1015Set Flow Volume0-99999 mLfloat (4 Bytes)
1018Working Time0.1-9999 sfloat (4 Bytes)
1020Working Mode0: Transferring, 1: Fixed volume measurement, 2: Fixed time and volumeunsigned short int (2 Bytes)
1021Pause Time0.1-9999 sfloat (4 Bytes)
1023Copy Numbers0-9999 times, 0 means infiniteunsigned short int (2 Bytes)

💡 Important Notes:

  • Registers 1015 and 1018 are invalid when the working mode is set to transferring.
  • Register 1018 is invalid when the working mode is set to Fixed volume measurement.
  • Registers 1002 and 1004 are invalid when the working mode is set to Fixed time and volume.
  • Ensure that the register data is set according to the chart provided, and avoid sending multiple register setups in a single order.

Calibration Parameters Setting

Address (Decimal) Name Range Data Type
2001 Testing time 0.5-9999 s unsigned short int (2 Bytes)
2002 Start/Stop test 1: Start, 0: Stop unsigned short int (2 Bytes)
2003 Actual volume 0-9999 mL float (4 Bytes)
2005 Restore Defaults 1: Restore calibration unsigned short int (2 Bytes)
2006 Micro Adjustment 1: Increase, 0: Decrease unsigned short int (2 Bytes)

Pump Head and Tubing Number Chart

Pump Head Name Pump Head Type Tubing Size Tubing Specific
EasyPumpI 0 13 13#
14 14#
19 19#
16 16#
25 25#
17 17#
18 18#
EasyPumpII 1 15 15#
24 24#
35 35#
36 36#
EasyPumpIII 2 13 13#
14 14#
19 19#
16 16#
25 25#
17 17#
18 18#
EasyPumpIV 3 15 15#
24 24#
35 35#
36 36#
EasyPumpV 4 13 13#
14 14#
19 19#
16 16#
25 25#
EasyPumpVI 5 13 13#
14 14#
19 19#
16 16#
25 25#
2*EasyPumpI 6 13 13#
14 14#
19 19#
16 16#
25 25#
17 17#
18 18#
2*EasyPumpII 7 15 15#
24 24#
35 35#
36 36#
2*EasyPumpIII 8 13 13#
14 14#
19 19#
16 16#
25 25#
17 17#
18 18#
YZ1515x 12 13 13#
14 14#
19 19#
16 16#
25 25#
17 17#
18 18#
YZ2515x 13 15 15#
24 24#
DZ25-3L 18 15 15#
24 24#
35 35#
36 36#
UC15 20 19 19#
16 16#
25 25#
17 17#
18 18#
UC25 21 24 24#
35 35#
36 36#
SN15 22 14 14#
16 16#
SN25 23 24 24#

💡 Important Note:

  • The table above is not exhaustive and includes only a subset of pump heads and tubing sizes. For the complete list, please refer to the documentation.

Examples of Command Strings

Example 1: Start the pump

  • Address: 01, indicates the device address to which the command is being sent.
  • Function Code: 06, used to write a single value to a register.
  • Register Address: 03F0, register address corresponding to the control register for starting the pump  (1008 in hexadecimal).
  • Data: 0001, written to the register to start the pump (1: Start, 0: Stop).
  • CRC: 487D, value used to ensure data integrity.

Therefore, the corresponding command string to start the pump is: 01 06 03 F0 00 01 48 7D.

Example 2: Set the pump speed to 20 rpm

  • Address: 01, indicates the device address to which the command is being sent.
  • Function Code: 10, used to write values to multiple registers.
  • Register Address: 03EA, register address corresponding to the speed setting register (1002 in hexadecimal).
  • Number of Registers: 0002, two registers will be written to.
  • Byte Count: 04, number of data bytes to be written.
  • Data: 41A00007,  represents the speed setting in float format corresponding to 20 RPM.
  • CRC: D76E, value used to ensure data integrity.

Therefore, the corresponding command string to set the pump speed to 20 rpm is: 01 10 03 EA 00 02 04 41 A0 00 07 D7 6E.

These 2 examples and other examples of  command strings are presented in the table below.

Task Command String
Start pump 01 06 03 F0 00 01 48 7D
Set direction to CCW 01 06 03 F1 00 00 D8 7D
Set direction to CW 01 06 03 F1 00 01 19 BD
Set speed to 20 RPM 01 10 03 EA 00 02 04 41 A0 00 07 D7 6E
Set flow rate to 50.0 mL/min 01 10 03 EC 00 02 04 42 48 00 00 7D 2C
Stop pump 01 06 03 F0 00 00 89 BD
Start full speed running 01 06 03 F2 00 01 E9 BD

How to Control the LabV Pumps Using Python ?

The LabV pump series can be controlled remotely using the MODBUS RTU protocol via Python, offering greater flexibility and convenience for running complex pumping programs with loops, pauses, and varying speeds/flow rates. This approach is particularly useful when integrating multiple devices, as it allows for the automation of sequences involving all connected equipment.

In this section, a dedicated custom Python library that utilizes the MODBUS RTU protocol will be introduced, allowing one to open the serial port and send control command strings to the LabV pump. We’ll cover examples that show how to set speed, start and stop the pump, set pump head and tube size, etc., providing a practical guide to using Python for controlling a LabV pump.

				
					'''
-----------------------------------------------------------
LABV Peristaltic Pump Control Library Using Modbus Protocol
-----------------------------------------------------------
'''

import serial
import struct

#------------------------------------------------------#
#   LIBRARY MANAGING THE COMMUNICATION WITH THE PUMP   #
#------------------------------------------------------#

class LABV:
    
    # Initialize the object and establish a serial connection with the corresponding data format
    def __init__(self, port, address=1, baudrate=9600, parity='E', bytesize=8, stopbits=1, timeout=1):
        self.ser = serial.Serial(port=port, baudrate=baudrate, parity=parity,
                                 bytesize=bytesize, stopbits=stopbits, timeout=timeout)
        self.address = address

    # Connect the pump
    def connect(self):
        if not self.ser.is_open:
            self.ser.open()

    # Close the serial connection
    def disconnect(self):
        if self.ser.is_open:
            self.ser.close()

    # Calculate the CRC (Cyclic Redundancy Check) for data integrity as
    # explained in "The MODBUS RTU Communication Protocol" section
    def _calculate_crc(self, data):
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc.to_bytes(2, byteorder='little')

    # Convert data into bytes for communication
    def _prepare_data(self, data, data_type):
        if data_type == 'float':
            return struct.pack('>f', data)
        elif data_type == 'int':
            return data.to_bytes(2, byteorder='big')
        else:
            raise ValueError("Data type not supported.")

    # Construct, send commands to the pump and verify the response
    def _send_command(self, function_code, register_address, data, data_type):
        data_bytes = self._prepare_data(data, data_type)
        payload = bytearray([self.address, function_code])
        payload.extend(register_address.to_bytes(2, byteorder='big'))

        if function_code == 0x10:  # Write multiple registers
            number_of_registers = len(data_bytes) // 2
            payload.extend(number_of_registers.to_bytes(2, byteorder='big'))
            payload.append(len(data_bytes))
            payload.extend(data_bytes)
        else:
            payload.extend(data_bytes)

        crc = self._calculate_crc(payload)
        payload.extend(crc)
        print(f"Sending payload: {payload.hex()}")  # Debug: Print the payload

        self.ser.write(payload)
        response = self.ser.read(8) # Adjust as per the device response length
        if len(response) != 8:
            raise ValueError("Invalid response length")
        if self._calculate_crc(response[:-2]) != response[-2:]:
            raise ValueError("Invalid CRC")
        return response

    #------------------------------#
    #   BASIC PARAMETERS SETTING   #
    #------------------------------#
    
    # Start the pump
    def start_pump(self):
        print("Starting pump...")
        response = self._send_command(0x06, 0x03F0, 0x0001, 'int')
        print(f"Start pump response: {response.hex()}")
        
    # Stop the pump
    def stop_pump(self):
        print("Stopping pump...")
        response = self._send_command(0x06, 0x03F0, 0x0000, 'int')
        print(f"Stop pump response: {response.hex()}")
        
    # Set the pump head
    def set_pump_head(self, pump_head):
        print(f"Setting pump head to {pump_head}...")
        response = self._send_command(0x06, 0x03E8, pump_head, 'int')
        print(f"Set pump head response: {response.hex()}")
        
    # Set the tube size
    def set_tube_size(self, tube_size):
        print(f"Setting tube size to {tube_size}...")
        response = self._send_command(0x06, 0x03E9, tube_size, 'int')
        print(f"Set tube size response: {response.hex()}")
        
    # Set the direction of rotation
    def set_direction(self, direction):
        print(f"Setting direction to {direction}...")
        data = 0x0001 if direction.upper() == "CW" else 0x0000
        response = self._send_command(0x06, 0x03F1, data, 'int')
        print(f"Set direction response: {response.hex()}")
        
    # Set the speed of the pump in RPM
    def set_speed(self, speed):
        print(f"Setting speed to {speed} RPM...")
        response = self._send_command(0x10, 0x03EA, speed, 'float')
        print(f"Set speed response: {response.hex()}")

    # Set the flow rate in mL/min
    def set_flow_rate(self, flow_rate):
        print(f"Setting flow rate to {flow_rate} mL/min...")
        response = self._send_command(0x10, 0x03EC, flow_rate, 'float')
        print(f"Set flow rate response: {response.hex()}")
        
    # Set the flow volume in mL
    def set_flow_volume(self, volume):
        print(f"Setting flow volume to {volume} mL...")
        response = self._send_command(0x10, 0x03F3, volume, 'float')
        print(f"Set flow volume response: {response.hex()}")

    # Set the working time in seconds
    def set_working_time(self, time):
        print(f"Setting working time to {time} seconds...")
        response = self._send_command(0x10, 0x03FA, time, 'float')
        print(f"Set working time response: {response.hex()}")

    # Set the working mode
    def set_working_mode(self, mode):
        """
        Working modes:
        0: Transferring
        1: Fixed volume measurement
        2: Fixed time and volume
        """
        if mode not in [0, 1, 2]:
            raise ValueError("Invalid working mode. Must be 0, 1, or 2.")
        
        print(f"Setting working mode to {mode}...")
        response = self._send_command(0x06, 0x03FC, mode, 'int')
        print(f"Set working mode response: {response.hex()}")
        
    # Set the pause time in seconds
    def set_pause_time(self, time):
        print(f"Setting pause time to {time} seconds...")
        response = self._send_command(0x10, 0x03FD, time, 'float')
        print(f"Set pause time response: {response.hex()}")

    # Set the number of copies
    def set_copy_numbers(self, numbers):
        print(f"Setting copy numbers to {numbers}...")
        response = self._send_command(0x06, 0x03FF, numbers, 'int')
        print(f"Set copy numbers response: {response.hex()}")

    # Set the back suction angle in degrees
    def set_back_suction_angle(self, angle):
        print(f"Setting back suction angle to {angle} degrees...")
        response = self._send_command(0x06, 0x03EF, angle, 'int')
        print(f"Set back suction angle response: {response.hex()}")

    # Start or stop full speed running
    def set_full_speed_running(self, start):
        state = "Start" if start else "Stop"
        print(f"{state} full speed running...")
        data = 0x0001 if start else 0x0000
        response = self._send_command(0x06, 0x03F2, data, 'int')
        print(f"{state} full speed running response: {response.hex()}")
        
    #---------------------------------------#
    #   CALIBRATION PARAMETERS SETTING UP   #
    #---------------------------------------#
    
    # Set the testing time in seconds
    def set_testing_time(self, time):
        print(f"Setting testing time to {time} seconds...")
        response = self._send_command(0x06, 0x07D1, time, 'int')
        print(f"Set testing time response: {response.hex()}")
        
    # Start the calibration test
    def start_test(self):
        print("Starting test...")
        response = self._send_command(0x06, 0x07D2, 0x0001, 'int')
        print(f"Start test response: {response.hex()}")
        
    # Stop the calibration test
    def stop_test(self):
        print("Stopping test...")
        response = self._send_command(0x06, 0x07D2, 0x0000, 'int')
        print(f"Stop test response: {response.hex()}")
        
    # Set the actual volume in mL
    def set_actual_volume(self, volume):
        print(f"Setting actual volume to {volume} mL...")
        response = self._send_command(0x10, 0x07D3, volume, 'float')
        print(f"Set actual volume response: {response.hex()}")
        
    # Restore calibration defaults
    def restore_defaults(self):
        print("Restoring defaults...")
        response = self._send_command(0x06, 0x07D5, 0x0001, 'int')
        print(f"Restore defaults response: {response.hex()}")
        
    # Perform micro adjustment (increase or decrease)
    def micro_adjustment(self, increase=True):
        action = "Increase" if increase else "Decrease"
        print(f"{action} micro adjustment...")
        data = 0x0001 if increase else 0x0000
        response = self._send_command(0x06, 0x07D6, data, 'int')
        print(f"{action} micro adjustment response: {response.hex()}")
        
#-----------------------------------------------------------------------#
#   EXAMPLES ON HOW TO USE THE DEFINED CLASS TO CONTROL THE LABV PUMP   #
#-----------------------------------------------------------------------#

if __name__ == "__main__":
    pump = LABV(port='COM6', address=1)  # Change port and address according to your setup
    pump.connect()

    try:
        pump.set_pump_head(5)
        pump.set_tube_size(16)
        pump.set_direction("CW")
        pump.set_speed(50)
        pump.set_flow_rate(50.0)
        pump.set_back_suction_angle(0)
        pump.set_full_speed_running(True)
        pump.start_pump()

    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        pump.stop_pump()
        pump.disconnect()

				
			
This Python library, to control the LabV pump series via the MODBUS RTU protocol, allows for precise and automated control over various pump parameters such as speed, direction, pump head, and tube size. Users of a LabV pump can now easily integrate their pump into some complex workflows involving multiple devices, enhancing operational efficiency and flexibility.

Conclusion

In this blog post, we’ve introduced the LabV peristaltic pump series and explored the basics of its external control Python using the MODBUS RTU communication protocol.  We hope this post serves as a valuable guide for researchers and engineers seeking to employ a LabV peristaltic pump in their microfluidics research and experiments.

🚨 While variations exist between the different series of Shenchen Lab pumps, we emphasize that the communication protocol remains almost unchanged. Whether you’re using the LabF series, LabN series, or any other, we trust that this content will also be beneficial for your future projects.

Stay tuned for more insights, tutorials, and practical applications in our future posts. Until then, happy pumping and coding 💻!

📧 If you have any questions or feedback, please feel free to contact us at support@darwin-microfluidics.com.