1047 encoder: setDataRate(50) is 20% slower than requested

Supporting 2.7 and 3.2+
Post Reply
stepheneb
Phidgetly
Posts: 19
Joined: Tue Oct 10, 2023 3:46 pm
Contact:

1047 encoder: setDataRate(50) is 20% slower than requested

Post by stepheneb »

When running the 1047 encoder at a datarate of 125 and collecting 125 samples the actual datarate as measured by accumulating the timeChange values passed to the onPositionChange() callback and dividing by the number of samples is 125.6 -- which is very close to the requested 125.

However when requesting a datarate of 50 and collecting 50 samples the effective datarate is 42.4.

And confusingly when requesting a datarate of 25 and collecting 50 samples the effective datarate is 25.5.

Examples of two runs, first at a datarate of 125 and second at 50. I only included the first and last three entries in each runs log output.

Updated version of encoder_1047.py with additional metrics included at end.

NOTE: I did try upgrading the firmware for the encoder from 200 to 301 -- no change in the results.

datarate=125

Code: Select all

% python3 encoder_1047.py --datarate=125 --samples=125 --channel=0

Press Enter to Start Test

Channel: 0 of 1047 4-channel high-speed encoder: 'PhidgetEncoder', 125 samples at 125 samples/s

sample            PositionChange    Position          Encoder Interval  System Interval   
1                 0                 0                 3.78              18.0              
2                 0                 0                 7.63              7.9               
3                 0                 0                 7.63              8.1               
...
123               0                 0                 8.90              8.0               
124               0                 0                 7.63              8.0               
125               0                 0                 7.63              8.0               

Channel: 0 of 1047 4-channel high-speed encoder: 'PhidgetEncoder', 125 samples at 125 samples/s

duration (system): 1.013
duration (encoder): 0.995

requested data rate: 125
effective data rate: 125.6
datarate=50

Code: Select all

% python3 encoder_1047.py --datarate=50 --samples=50 --channel=0

Press Enter to Start Test

Channel: 0 of 1047 4-channel high-speed encoder: 'PhidgetEncoder', 50 samples at 50 samples/s

sample            PositionChange    Position          Encoder Interval  System Interval   
1                 0                 0                 3.78              19.0              
2                 0                 0                 24.13             23.8              
3                 0                 0                 24.15             24.2              
...
48                0                 0                 24.16             24.0              
49                0                 0                 24.14             24.0              
50                0                 0                 24.14             24.0              

Channel: 0 of 1047 4-channel high-speed encoder: 'PhidgetEncoder', 50 samples at 50 samples/s

duration (system): 1.198
duration (encoder): 1.180

requested data rate: 50
effective data rate: 42.4
updated version of encoder_1047.py

Code: Select all

from Phidget22.PhidgetException import *
from Phidget22.Phidget import *
from Phidget22.Devices.Encoder import *

import argparse

import time
import json

# connect phidget 1047 high-speed 4-channel encoder to usb on computer

# When True this program uses ENC1000_0 encoder connected to VINT
# Useful for development/testing without encoder_1047 available
use_encoder_ENC1000 = False

encoder_channel = 0
datarate = 125
max_samples = 2 * datarate

encoder_pulley_hubport = 0

#
# Parse optional command line arguments
#
parser = argparse.ArgumentParser(
    description='test communication with Phidget 1047 4-channel high-speed encoder')
parser.add_argument('--enc1000', default=False, action=argparse.BooleanOptionalAction,
                    help=f"use ENC1000_0 encoder connected to VINT hub, default: {False}")
parser.add_argument('--channel', type=int, default=encoder_channel,
                    help=f"encoder channel (0-3), default: {encoder_channel}")
parser.add_argument('--datarate', type=int, default=datarate,
                    help=f"encoder data rate (1-125), default: {datarate}")
parser.add_argument('--samples', type=int, default=max_samples,
                    help=f"encoder samples to collect, default: {max_samples}")
args = parser.parse_args()

encoder_channel = args.channel
datarate = args.datarate
max_samples = args.samples
use_encoder_ENC1000 = args.enc1000

if use_encoder_ENC1000:
    datarate = min(50, datarate)
else:
    datarate = min(125, datarate)

encoder_counts_per_revolution = 2000
encoderPosition = 0


#
# Load saved vint and encoder_1047 serial numbers
#
# example json: phidget-serial-numbers.json
#
#   {
#      "vint": 538832,
#      "encoder_1047": 538833
#    }
#
try:
    filename = "phidget-serial-numbers.json"
    sample_filename = "phidget-serial-numbers-sample.json"
    serial_numbers = json.load(open(filename))
    vint_serial_number = serial_numbers['vint']
    encoder_1047_serial_number = serial_numbers['encoder_1047']
except FileNotFoundError:
    print(f"""
          ***
          *** Required file: '{filename}' not found
          *** Copy file: '{sample_filename}' to '{filename}'
          ***
          ***   cp {sample_filename} {filename}'
          ***
          *** and update the serial numbers for the vint hub and 1047 encoder
          ***
          """)
    raise
except json.decoder.JSONDecodeError:
    print(f"""
          *** JSON decoding error in file: '{filename}'
          """)
    raise


sample_count = 0

start_time_sys = 0
end_time_sys = 0
previous_time_sys = 0

duration = 0
duration_sys = 0

log = ""
about = ""

# log formatting
col_width = 18
header_str = f"\n{'sample':<{col_width}}{'PositionChange':<{col_width}}{'Position':<{col_width}}{'Encoder Interval':<{col_width}}{'System Interval':<{col_width}}\n"


def onPositionChange(self, positionChange, timeChange, indexTriggered):
    global previous_time_sys, duration
    global log, sample_count, encoderPosition

    sample_count += 1
    if sample_count <= max_samples:
        timeChange += 0.00001
        interval = timeChange / 1000
        duration += interval
        encoderPositionChangePerSecond = 1 / interval * positionChange
        rps = encoderPositionChangePerSecond / encoder_counts_per_revolution
        encoderPosition += positionChange
        rotation = encoderPosition / encoder_counts_per_revolution

        encoderPosition = self.getPosition()
        current_time_sys = time.time()
        interval_sys = (current_time_sys - previous_time_sys)
        interval_ms_sys = interval_sys * 1000
        previous_time_sys = current_time_sys

        log += f"{sample_count:<{col_width}}{positionChange:<{col_width}.0f}{encoderPosition:<{col_width}}{timeChange:<{col_width}.2f}{interval_ms_sys:<{col_width}.1f}\n"


def main():
    global log, about, start_time_sys, end_time_sys, previous_time_sys, duration, duration_sys
    log = header_str
    about = ""

    try:
        input("\nPress Enter to Start Test\n")
    except (Exception, KeyboardInterrupt):
        pass

    en0 = Encoder()

    if use_encoder_ENC1000:
        en0.setDeviceSerialNumber(vint_serial_number)
        en0.openWaitForAttachment(5000)
        encoder_classname = en0.getDeviceClassName()
        en0.setHubPort(encoder_pulley_hubport)
        en0.setDataRate(datarate)
        about += f"Using VINT ENC1000_0 encoder: '{encoder_classname}'"
    else:
        en0.setDeviceSerialNumber(encoder_1047_serial_number)
        en0.setChannel(encoder_channel)
        en0.openWaitForAttachment(5000)
        encoder_classname = en0.getDeviceClassName()
        en0.setDataRate(datarate)
        about += f"Channel: {encoder_channel} of 1047 4-channel high-speed encoder: '{encoder_classname}'"

    about += f", {max_samples} samples at {datarate} samples/s"
    print(about)
    en0.setOnPositionChangeHandler(onPositionChange)
    start_time_sys = time.time()
    previous_time_sys = start_time_sys
    duration = 0

    while sample_count < max_samples:
        pass

    en0.close()
    end_time_sys = time.time()
    duration_sys = end_time_sys - start_time_sys


main()

print(f"{log}")
print(about)

effective_datarate = 1 / (duration / max_samples)

print(f"""
duration (system): {duration_sys:.3f}
duration (encoder): {duration:.3f}

requested data rate: {datarate}
effective data rate: {effective_datarate:.1f}
""")
jdecoux
Labview Developer
Posts: 163
Joined: Mon Nov 13, 2017 10:20 am
Contact:

Re: 1047 encoder: setDataRate(50) is 20% slower than requested

Post by jdecoux »

The 1047, like many USB Phidgets based on older designs, is forced to have a DataInterval of multiples of 8ms.
stepheneb
Phidgetly
Posts: 19
Joined: Tue Oct 10, 2023 3:46 pm
Contact:

Re: 1047 encoder: setDataRate(50) is 20% slower than requested

Post by stepheneb »

That seems to work properly when requesting a datarates of 1, 2, 48, 16, 32, 64. However when setting the datarate to 96 the effective datarate is only 63.

Code: Select all

% python3 encoder_1047.py --datarate=96 --samples=96 --channel=0

...

Channel: 0 of 1047 4-channel high-speed encoder: 'PhidgetEncoder', 96 samples at 96 samples/s

duration (system): 1.543
duration (encoder): 1.523

requested data rate: 96
effective data rate: 63.0
There are also errors in effective data rate when run at other datarates that are multiples of 8ms like 24 (21.0) and 48 (41.8).
jdecoux
Labview Developer
Posts: 163
Joined: Mon Nov 13, 2017 10:20 am
Contact:

Re: 1047 encoder: setDataRate(50) is 20% slower than requested

Post by jdecoux »

To clear up some confusion, 96Hz is not a multiple of 8ms.

Achievable data rates for this device would be:
8ms: 125Hz
16ms: 62.5Hz
24ms: 41.667Hz
32ms: 31.25Hz
40ms: 25Hz
48ms: 20.833Hz
56ms: 17.857Hz
64ms: 15.625Hz
72ms: 13.889Hz
80ms: 12.5Hz
etc.
User avatar
Patrick
Lead Developer
Posts: 616
Joined: Mon Jun 20, 2005 8:46 am
Location: Canada
Contact:

Re: 1047 encoder: setDataRate(50) is 20% slower than requested

Post by Patrick »

After setting the DataRate, you can read it back to get the actual rate being used by the device.

-Patrick
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 52 guests