Accurate Delivery for use in fluid pumps

Overview

PyPi module N/A
git repository https://bitbucket.org/arrizza-public/accurate-delivery
git command git clone git@bitbucket.org:arrizza-public/accurate-delivery.git
Verification Report https://arrizza.com/web-ver/cpp-accurate-delivery-report.html
Version Info
OS Language #Runs Last Run Cov%
Ubuntu 24.04 noble Python 3.10 1687 2025-01-31 -

Summary

This C++ library is an algorithm to be used for accurate delivery of fluids.

It assumes there is access to a Real Time Clock (RTC) that can give an accurate elapsed time since the start of the delivery.

The current time is passed in, it returns the number of pulses or encoder ticks needed for the stepper or DC motor to take to deliver the correct amount of fluid at that moment.

Accuracy

The accuracy of the algorithm is dependent on several things:

RTC

  • the RTC is correctly initialized
  • the correct elapsed time in seconds since the start of the delivery is passed in
  • the RTC is accurate to at least the millisecond, preferably to the microsecond
  • the RTC value can be obtained and passed to the algorithm quickly. If there is a significant delay, the value passed in may not accurately reflect the actual elapsed time since the start of the delivery
  • a pause in the delivery is considered a new delivery. The elapsed time will need to be recalculated

pulses

  • the pulses can be delivered relatively quickly and smoothly. The elapsed time can vary i.e. 1.05ms vs 1.1mS etc. But if that time is too variable there may be segments of the delivery that have a lag in the pulsing of the motor.
  • If the there is a mechanical requirement, that is up to the caller to do efficiently. For example, if the pump needs to be reset or moved without fluid delivery, then again those things should be done as quickly and smoothly as possible.

fluid delivery

  • the calculation for micro-liters (uL) per pulse assumes that the delivery of the fluid is consistent and reliable. So 1 pulse at any time delivers the same amount no matter when it is delivered.
  • the values for uL/rev and pulses/rev are accurate and consistent
    • the uL/rev value is correct no matter which revolution of the motor is done
    • the pulses/rev value is correct no matter which revolution of the motor is done

rates and delivery time

  • slow rates (e.g. 0.001 mL/hr) are less accurate simply because there are fewer pulses being delivered. Higher rates are more accurate
  • total delivery times affect the overall accuracy as well. For example at slow rates, delivering for 2 hours vs 1 hour will increase overall rate accuracy significantly.

Compilation

You can use float or double for the basic calculation type. Floats have been tested to 0.001 mL/hr and have very low error rates.

Use "ad_type = double" in accurate_delivery.h to convert to higher accuracy.

This seems to get an extra decimal digit of accuracy.

How to use

// example motor values
static const float UL_PER_REV = 7.66;    // uL/rev
static const float PULSES_PER_REV = 166; // 165 worst; 166 best

float const rate = 0.1; // mL/hr

auto bt = AccurateDelivery();
bt.set_cfg(UL_PER_REV, PULSES_PER_REV);
bt.init(rate);

bool delivering = true;
float start_time = get_rtc(); // get the RTC value in seconds
while (delivering) {
    curr_time = get_rtc();                        // get the RTC value in seconds
    float const elapsed = curr_time - start_time; // calc total elapsed time

    // wait at least a millisecond
    if (elapsed < 0.001) {
        continue;
    }

    pulses_to_do += bt.calc_pulses(elapsed);
    pulse_the_motor(pulses_to_do); // function to move the motor/pump
}

Run

See sample/app.cpp to simulate the algorithm at various rates

./doit             # runs the default rate (9135.038 ml/hr see app.cpp)
./doit  0.001      # runs 0.001 ml/hr

./doit worst       # finds the rates between 0.010 and 10000;
                   # runs 5000 random values; takes about 5 minutes

./doit worst low   # finds the rates between 0.001 and 10.0;
                   # runs 5000 random values; takes about 9 minutes

./doit stats       # runs a delivery and calculates std dev, min/max and mean errors
                   # in the difference between actual vs expected values of pulses, rate and volume 
./doit stats 1.23  # runs stats on delivery of 1.23 mL/hr

Run from Python & PC (not embedded)

The script pyrun/main.py creates a Singleton of AccurateDelivery and runs a delivery. It wraps a single instance of the C++ AccurateDelivery class and makes it available to python via ctypes module.

It uses the RTC on your PC via time.time() to get the current time. To get an accurate periodic 1ms call in python, see Accurate Timed Loop

./do_pyrun

---> starting 300 sec delivery
running init
   pulses rev:          166.000000 pulses/rev
   vol per rev:           7.660000 ul/rev
   requested rate:       10.000000 mL/hr
   rate in ul:        10000.000000 uL/hr
   rate ul/sec:           2.777778 ul/sec
   rate ul/ms:            0.002778 ul/ms
   pulses per ul:        21.671019 pulses/ul
   pulses per ms:         0.060197 pulses
   ul/pulse:              0.046145 uL/pulse
...... ......   1
...... ......   2
...... ......   3
...... ......   4
...... ......   5

---> delivery complete
     act rate :        9.999858 ml/hr
     exp rate :       10.000000 ml/hr
     elapsed  :      300.001243 secs
     pulses   :    18059        pulses
     total vol:      833.324940 uL

- John Arrizza