lis3dh - meassure shake speed / read & print to slow?

CircuitPython on hardware including Adafruit's boards, and CircuitPython libraries using Blinka on host computers.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
slight
 
Posts: 120
Joined: Wed Sep 12, 2012 2:23 am

lis3dh - meassure shake speed / read & print to slow?

Post by slight »

Hello community,

iam currently experimenting with an Adafruit LIS3DH Triple-Axis Accelerometer breakout board and CP.

i currently use a Adafruit ESP32-S3 Reverse TFT Feather as controller board.

my original need is to detect the start & end point of each shake-move and the duration. so i know some sort of speed and the start point for the next shake move...
(i know there is an internal shake detection - but that gives me just a Yes / No)

my first idea was to just print the current values as fast as possible and plot them on my computer.
for the plotting i am using SerialPlot
so i can analyze how this all happens... and then try to implement an simple *algorithm* to detect & measure start stop and duration..
(some if else statements in the simplest case)

during these experiments i found that the value updates were relative slow and i did not see every direction change of my movements.
so i dig deeper and came up with this speed test:

Code: Select all

# based on example
# https://github.com/adafruit/Adafruit_CircuitPython_LIS3DH/blob/main/examples/lis3dh_simpletest.py
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import busio
import adafruit_lis3dh

# uses board.SCL and board.SDA
i2c = board.I2C()
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

# RANGE_2_G
# RANGE_4_G
# RANGE_8_G
# RANGE_16_G
lis3dh.range = adafruit_lis3dh.RANGE_16_G


def speed_test(fn, msg, count=1000):
    print("{} speed test running..".format(msg))
    start = time.monotonic()
    for i in range(count):
        fn()
    end = time.monotonic()
    duration = (end - start) / count
    duration_ms = duration * 1000
    result = {
        "msg": msg,
        "duration_ms": duration_ms,
    }
    print(
        "'{}'  needs {:10.3f}ms/call".format(
            result["msg"],
            result["duration_ms"],
        )
    )
    return result


def print_results(speed_tests):
    msg_max_length = max(len(x["msg"]) for x in speed_tests)
    # print(msg_max_length)
    for test in speed_tests:
        msg_template = (
            "'{:<" + "{}".format(msg_max_length + 5) + "}'  needs {:10.3f}ms/call"
        )
        print(
            msg_template.format(
                test["msg"],
                test["duration_ms"],
            )
        )


print("\n" * 20)
speed_tests = []


lis3dh.data_rate = adafruit_lis3dh.DATARATE_1_HZ  # default → 1000ms
speed_tests.append(
    speed_test(
        lambda: lis3dh.acceleration[1],
        msg="lis3dh.acceleration[1] → 1Hz (1000ms)",
    )
)

lis3dh.data_rate = adafruit_lis3dh.DATARATE_400_HZ  # default → 2,5ms
speed_tests.append(
    speed_test(
        lambda: lis3dh.acceleration[1],
        msg="lis3dh.acceleration[1] → 400Hz (2.5ms)",
    )
)

lis3dh.data_rate = adafruit_lis3dh.DATARATE_LOWPOWER_5KHZ  # → 0,2ms
speed_tests.append(
    speed_test(
        lambda: lis3dh.acceleration[1],
        msg="lis3dh.acceleration[1] → 5kHz (0,2ms)",
    )
)

speed_tests.append(
    speed_test(
        lambda: print(3.14159),
        msg="print(i * 3.14159)",
    )
)

speed_tests.append(
    speed_test(
        lambda: print(lis3dh.acceleration[1]),
        msg="print(lis3dh.acceleration[1])",
        count=50,
    )
)


print("\n" * 20)
time.sleep(1)

print_results(speed_tests)
time.sleep(1)

print("done...")

if i run this i get

Code: Select all

# a bunch of output......
'lis3dh.acceleration[1] → 1Hz (1000ms)     '  needs      2.882ms/call
'lis3dh.acceleration[1] → 400Hz (2.5ms)   '  needs      2.886ms/call
'lis3dh.acceleration[1] → 5kHz (0,2ms)    '  needs      2.861ms/call
'print(i * 3.14159)                         '  needs      2.284ms/call
'print(lis3dh.acceleration[1])              '  needs     20.283ms/call
done...

i thougth that i get different speeds with the different update-settings.....
but so or so - the limiting factor is the combination of the read and the print...
and i have no idea why..

anyone any ideas / insights tips?!
also for my orig. goal to detect & measure shake or other movements?

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by adafruit_support_bill »

The data rate of the sensor is the rate at which it updates its data registers with new readings. That is entirely independent of how fast you poll them from your code.

User avatar
slight
 
Posts: 120
Joined: Wed Sep 12, 2012 2:23 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by slight »

good point.
and it seems i am not able to poll faster than the ~2.9ms interval...
so i can leave the internal refresh-rate at the default 400Hz and hope that my code is fast enough for everything i want ;-)

leaves the question open why the poll & print is so slow...

User avatar
slight
 
Posts: 120
Joined: Wed Sep 12, 2012 2:23 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by slight »

some more testing...
if i deactivate the internal display refresh with

Code: Select all

print("deactivate display..")
board.DISPLAY.auto_refresh = False
board.DISPLAY.root_group = displayio.Group()
so it only prints to the serial port i save ~30ms per print in this shake -test code:

Code: Select all

# SPDX-FileCopyrightText: 2023 Stefan Krüger s-light.eu
# SPDX-License-Identifier: MIT

import time
import board
import busio
import displayio
import adafruit_lis3dh

i2c = board.I2C()
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

# Set range of accelerometer (can be RANGE_2_G, RANGE_4_G, RANGE_8_G or RANGE_16_G).
lis3dh.range = adafruit_lis3dh.RANGE_16_G

# setup reading speed
# https://docs.circuitpython.org/projects/lis3dh/en/latest/api.html#adafruit_lis3dh.LIS3DH.data_rate
# 2.5ms interval → DEFAULT
# lis3dh.data_rate = DATARATE_400_HZ
# 0.02ms interval
# lis3dh.data_rate = DATARATE_LOWPOWER_5KHZ
print("lis3dh.data_rate", lis3dh.data_rate)

direction_old = 0
start_time = 0
duration = 0

print("deactivate display..")
board.DISPLAY.auto_refresh = False
board.DISPLAY.root_group = displayio.Group()

print("loop...")
while True:
    # Read accelerometer values (in m / s ^ 2).
    # Returns a 3-tuple of x, y, z axis values.
    y = lis3dh.acceleration[1]
    if y > 5 and direction_old is not 1:
        direction_old = 1
        duration = (time.monotonic() - start_time) * 1000
        start_time = time.monotonic()
        print('→ {:7.3f}'.format(duration))
    if y < -5 and direction_old is not 0:
        direction_old = 0
        duration = (time.monotonic() - start_time) * 1000
        start_time = time.monotonic()
        print('← {:7.3f}'.format(duration))
    
    time.sleep(0.0001)

board.DISPLAY.auto_refresh = True
board.DISPLAY.root_group = displayio.CIRCUITPYTHON_TERMINAL
i think now the main part is the print and format that maybe limits me...

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by adafruit_support_bill »

Any kind of I/O is likely to slow you down. Drawing to a display much more so than simple serial output. And Python, being interpreted, is not the fastest language around.

You could save yourself the overhead of the formatting by streaming just the raw binary data and handle any necessary conversions on your computer.

User avatar
slight
 
Posts: 120
Joined: Wed Sep 12, 2012 2:23 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by slight »

some more experiments:
with a disabled build in display i get ~3ms updates: :-)

Code: Select all

# SPDX-FileCopyrightText: 2023 s-light.eu Stefan Krüger
# SPDX-License-Identifier: MIT

import time
import board
import busio
import displayio
import adafruit_lis3dh

i2c = board.I2C()
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

# RANGE_2_G
# RANGE_4_G
# RANGE_8_G
# RANGE_16_G
lis3dh.range = adafruit_lis3dh.RANGE_16_G

# default → 2,5ms update rate
lis3dh.data_rate = adafruit_lis3dh.DATARATE_400_HZ 


print("deactivate display..")
board.DISPLAY.auto_refresh = False
board.DISPLAY.root_group = displayio.Group()

try:
    while True:
        # print single accelerometer value (in m / s ^ 2).
        print(lis3dh.acceleration[1])

        # Small delay to keep things responsive but give time for interrupt processing.
        time.sleep(0)
except KeyboardInterrupt:
    print("exit..")
finally:
    board.DISPLAY.auto_refresh = True
    board.DISPLAY.root_group = displayio.CIRCUITPYTHON_TERMINAL
results in

Code: Select all

[0.003] 3.5632
[0.003] 8.04595
[0.003] 12.7586
[0.003] 23.4482
[0.003] 28.8505
[0.003] 41.1493
[0.003] 46.6665
[0.003] 51.3791
[0.003] 57.4711
[0.002] 59.0802
[0.003] 59.54
[0.003] 58.0457
[0.003] 56.5515
[0.003] 54.2527
[0.003] 47.3561
[0.003] 43.1033
[0.003] 34.5976
[0.003] 30.5746
[0.003] 26.6666
[0.003] 17.816
[0.003] 12.5287
[0.003] 7.70112
[0.003] -2.75861
[0.003] -8.39077
[0.003] -13.908
[0.003] -23.6781
[0.003] -29.0804
[0.003] -33.793
[0.003] -41.3792
[0.003] -43.563
[0.002] -47.4711
[0.003] -48.5056
[0.003] -48.6205
[0.003] -46.6665
[0.003] -44.5975
[0.003] -41.609
[0.003] -34.7125
[0.003] -30.3447
[0.003] -21.839
[0.003] -17.2413
[0.003] -12.9885
[0.003] -4.71263
[0.003] -0.57471
[0.003] 3.44826
[0.003] 10.9195
[0.003] exit..
[0.000] 0;🐍Wi-Fi: off | Done | 8.0.5
[0.000] Code done running.
[0.130] 
[0.000] Press any key to enter the REPL. Use CTRL-D to reload.
(using minicom with Alt+N → Timestamp delta between lines)
yeah!
the internal display update was the *show stopper*!

as my fist *i2c read speed test* resulted in ~2.8ms intervals i think i do not need to try to optimize more...

just for the completeness:
@adafruit_support_bill:
did you mean to write the data in raw with the help of the usb_cdc direct stream access?
a simple

Code: Select all

print(lis3dh.acceleration[1])
does a *default formatting*...

i researched a bit and came up with this direct *write*:

Code: Select all

usb_cdc.console.write(bytes(int(lis3dh.acceleration[1]*10)))
but this seems to crash some how..

so i just extended my speedtest with the now display variant to thest how much impact the formating has:

Code: Select all

# based on example
# https://github.com/adafruit/Adafruit_CircuitPython_LIS3DH/blob/main/examples/lis3dh_simpletest.py
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import displayio
import busio
import adafruit_lis3dh

# uses board.SCL and board.SDA
i2c = board.I2C()
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

# RANGE_2_G
# RANGE_4_G
# RANGE_8_G
# RANGE_16_G
lis3dh.range = adafruit_lis3dh.RANGE_16_G


def speed_test(fn, msg, count=1000):
    print("{} speed test running..".format(msg))
    start = time.monotonic()
    for i in range(count):
        fn()
    end = time.monotonic()
    duration = (end - start) / count
    duration_ms = duration * 1000
    result = {
        "msg": msg,
        "duration_ms": duration_ms,
    }
    print(
        "'{}'  needs {:10.3f}ms/call".format(
            result["msg"],
            result["duration_ms"],
        )
    )
    return result


def print_results(speed_tests):
    msg_max_length = max(len(x["msg"]) for x in speed_tests)
    # print(msg_max_length)
    for test in speed_tests:
        msg_template = (
            "'{:<" + "{}".format(msg_max_length + 5) + "}'  needs {:10.3f}ms/call"
        )
        print(
            msg_template.format(
                test["msg"],
                test["duration_ms"],
            )
        )


print("\n" * 20)
speed_tests = []


# lis3dh.data_rate = adafruit_lis3dh.DATARATE_1_HZ  # default → 1000ms
# speed_tests.append(
#     speed_test(
#         lambda: lis3dh.acceleration[1],
#         msg="lis3dh.acceleration[1] → 1Hz (1000ms)",
#     )
# )

lis3dh.data_rate = adafruit_lis3dh.DATARATE_400_HZ  # default → 2,5ms
speed_tests.append(
    speed_test(
        lambda: lis3dh.acceleration[1],
        msg="lis3dh.acceleration[1] → 400Hz (2.5ms)",
    )
)


# lis3dh.data_rate = adafruit_lis3dh.DATARATE_LOWPOWER_5KHZ  # → 0,2ms
# speed_tests.append(
#     speed_test(
#         lambda: lis3dh.acceleration[1],
#         msg="lis3dh.acceleration[1] → 5kHz (0,2ms)",
#     )
# )


speed_tests.append(
    speed_test(
        lambda: print(3.14159),
        msg="print(i * 3.14159)",
    )
)

lis3dh.data_rate = adafruit_lis3dh.DATARATE_400_HZ  # default → 2,5ms

speed_tests.append(
    speed_test(
        lambda: print(lis3dh.acceleration[1]),
        msg="print(lis3dh.acceleration[1]) 400Hz",
        count=50,
    )
)



print("deactivate display..")
board.DISPLAY.auto_refresh = False
board.DISPLAY.root_group = displayio.Group()

speed_tests.append(
    speed_test(
        lambda: print(lis3dh.acceleration[1]),
        msg="print(lis3dh.acceleration[1]) 400Hz no display",
    )
)

speed_tests.append(
    speed_test(
        lambda: print(3.14159),
        msg="print(i * 3.14159)",
    )
)

speed_tests.append(
    speed_test(
        lambda: print("{:10.3f}".format(lis3dh.acceleration[1])),
        msg='print("{:10.3f}".format(lis3dh.acceleration[1])) 400Hz no display',
    )
)

speed_tests.append(
    speed_test(
        lambda: print("{:10.3f} {:10.3f} {:10.3f}".format(*lis3dh.acceleration)),
        msg='print("{:10.3f} {:10.3f} {:10.3f}".format(*lis3dh.acceleration)) 400Hz no display',
    )
)



print("activate display..")
board.DISPLAY.auto_refresh = True
board.DISPLAY.root_group = displayio.CIRCUITPYTHON_TERMINAL




print("\n" * 20)
time.sleep(1)

print_results(speed_tests)
time.sleep(1)

print("done...")

resutls:

Code: Select all

[1.003] 'lis3dh.acceleration[1] → 400Hz (2.5ms)                                              '  needs      2.879ms/call
[0.002] 'print(i * 3.14159)                                                                    '  needs      2.355ms/call
[0.001] 'print(lis3dh.acceleration[1]) 400Hz                                                   '  needs     20.312ms/call
[0.002] 'print(lis3dh.acceleration[1]) 400Hz no display                                        '  needs      3.383ms/call
[0.069] 'print(i * 3.14159)                                                                    '  needs      0.281ms/call
[0.002] 'print("{:10.3f}".format(lis3dh.acceleration[1])) 400Hz no display                     '  needs      3.852ms/call
[0.002] 'print("{:10.3f} {:10.3f} {:10.3f}".format(*lis3dh.acceleration)) 400Hz no display     '  needs      4.070ms/call
[1.000] done...



so my conclusion:
cp is fast (enough for me) :-)
just use your favorite formatting - and deactivate the internal display :-)

sunny greetings
stefan

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by adafruit_support_bill »

The 'write' function from usb_cdc was what I was referring to. Not sure why that didn't work for you. But it sounds like you have a workable solution. Good luck with your project!

User avatar
slight
 
Posts: 120
Joined: Wed Sep 12, 2012 2:23 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by slight »

thanks for all your help!

User avatar
richpaul6806
 
Posts: 16
Joined: Sun May 21, 2023 12:39 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by richpaul6806 »

I saw your post with various times to complete various tasks. Just curious if you ever measured increasing the speed of the I2C bus

User avatar
slight
 
Posts: 120
Joined: Wed Sep 12, 2012 2:23 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by slight »

@richpaul6806
you mean
https://docs.circuitpython.org/en/lates ... #busio.I2C
to initialize with 400kHz?
400 kHz is the max Frequency as of the datasheet: 2.4.2 I2 C - Inter IC control interface

good point i should try this!

User avatar
slight
 
Posts: 120
Joined: Wed Sep 12, 2012 2:23 am

Re: lis3dh - meassure shake speed / read & print to slow?

Post by slight »

new board QT Py ESP32-S3

new test code:

Code: Select all

# based on example
# https://github.com/adafruit/Adafruit_CircuitPython_LIS3DH/blob/main/examples/lis3dh_simpletest.py
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import displayio
import busio
import adafruit_lis3dh

# uses board.SCL and board.SDA
if hasattr(board, "STEMMA_I2C"):
    i2c = board.STEMMA_I2C()
elif hasattr(board, "I2C"):
    i2c = board.I2C()
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)

# RANGE_2_G
# RANGE_4_G
# RANGE_8_G
# RANGE_16_G
lis3dh.range = adafruit_lis3dh.RANGE_16_G


def speed_test(fn, msg, count=1000):
    print("{} speed test running..".format(msg))
    start = time.monotonic()
    for i in range(count):
        fn()
    end = time.monotonic()
    duration = (end - start) / count
    duration_ms = duration * 1000
    result = {
        "msg": msg,
        "duration_ms": duration_ms,
    }
    print(
        "'{}'  needs {:10.3f}ms/call".format(
            result["msg"],
            result["duration_ms"],
        )
    )
    return result


def print_results(speed_tests):
    msg_max_length = max(len(x["msg"]) for x in speed_tests)
    # print(msg_max_length)
    for test in speed_tests:
        msg_template = (
            "'{:<" + "{}".format(msg_max_length + 5) + "}'  needs {:10.3f}ms/call"
        )
        print(
            msg_template.format(
                test["msg"],
                test["duration_ms"],
            )
        )


print("\n" * 20)
speed_tests = []


# lis3dh.data_rate = adafruit_lis3dh.DATARATE_1_HZ  # default → 1000ms
# speed_tests.append(
#     speed_test(
#         lambda: lis3dh.acceleration[1],
#         msg="lis3dh.acceleration[1] → 1Hz (1000ms)",
#     )
# )

lis3dh.data_rate = adafruit_lis3dh.DATARATE_400_HZ  # default → 2,5ms
speed_tests.append(
    speed_test(
        lambda: lis3dh.acceleration[1],
        msg="lis3dh.acceleration[1] → 400Hz (2.5ms)",
    )
)


# lis3dh.data_rate = adafruit_lis3dh.DATARATE_LOWPOWER_5KHZ  # → 0,2ms
# speed_tests.append(
#     speed_test(
#         lambda: lis3dh.acceleration[1],
#         msg="lis3dh.acceleration[1] → 5kHz (0,2ms)",
#     )
# )


speed_tests.append(
    speed_test(
        lambda: print(3.14159),
        msg="print(i * 3.14159)",
    )
)

lis3dh.data_rate = adafruit_lis3dh.DATARATE_400_HZ  # default → 2,5ms

speed_tests.append(
    speed_test(
        lambda: print(lis3dh.acceleration[1]),
        msg="print(lis3dh.acceleration[1]) 400Hz",
        count=50,
    )
)


# ++++++++++++++++++++++++++++++++++++++++++
#  now with High Speed / without Display
# ++++++++++++++++++++++++++++++++++++++++++
msg=" fastI2C"
if hasattr(board, "DISPLAY"):
    print("deactivate display..")
    board.DISPLAY.auto_refresh = False
    board.DISPLAY.root_group = displayio.Group()
    msg=" no display"

i2c.deinit()
if hasattr(board, "STEMMA_I2C"):
    i2c = busio.I2C(scl=board.SCL1, sda=board.SDA1, frequency=400000)
elif hasattr(board, "I2C"):
    i2c = busio.I2C(scl=board.SCL, sda=board.SDA, frequency=400000)
lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c)


speed_tests.append(
    speed_test(
        lambda: print(lis3dh.acceleration[1]),
        msg="print(lis3dh.acceleration[1]) 400Hz" + msg,
    )
)

speed_tests.append(
    speed_test(
        lambda: print(lis3dh.read_adc_raw(1)),
        msg="print(lis3dh.read_adc_raw(1)) 400Hz" + msg,
    )
)

speed_tests.append(
    speed_test(
        lambda: print(3.14159),
        msg="print(i * 3.14159)" + msg,
    )
)

speed_tests.append(
    speed_test(
        lambda: print("{:10.3f}".format(lis3dh.acceleration[1])),
        msg='print("{:10.3f}".format(lis3dh.acceleration[1])) 400Hz' + msg,
    )
)

speed_tests.append(
    speed_test(
        lambda: print("{:10.3f} {:10.3f} {:10.3f}".format(*lis3dh.acceleration)),
        msg='print("{:10.3f} {:10.3f} {:10.3f}".format(*lis3dh.acceleration)) 400Hz' + msg,
    )
)



lis3dh.data_rate = adafruit_lis3dh.DATARATE_LOWPOWER_5KHZ  # → 0,2ms
speed_tests.append(
    speed_test(
        lambda: print(lis3dh.read_adc_raw(1)),
        msg="print(lis3dh.read_adc_raw(1)) 5kHz" + msg,
    )
)

if hasattr(board, "DISPLAY"):
    print("activate display..")
    board.DISPLAY.auto_refresh = True
    board.DISPLAY.root_group = displayio.CIRCUITPYTHON_TERMINAL




print("\n" * 20)
time.sleep(1)

print_results(speed_tests)
time.sleep(1)

print("done...")

results:

Code: Select all

'lis3dh.acceleration[1] → 400Hz (2.5ms)                                           '  needs      1.847ms/call
'print(i * 3.14159)                                                                 '  needs      1.260ms/call
'print(lis3dh.acceleration[1]) 400Hz                                                '  needs      2.402ms/call
'print(lis3dh.acceleration[1]) 400Hz fastI2C                                        '  needs      1.520ms/call
'print(lis3dh.read_adc_raw(1)) 400Hz fastI2C                                        '  needs      0.937ms/call
'print(i * 3.14159) fastI2C                                                         '  needs      1.408ms/call
'print("{:10.3f}".format(lis3dh.acceleration[1])) 400Hz fastI2C                     '  needs      1.880ms/call
'print("{:10.3f} {:10.3f} {:10.3f}".format(*lis3dh.acceleration)) 400Hz fastI2C     '  needs      5.312ms/call
'print(lis3dh.read_adc_raw(1)) 5kHz fastI2C                                         '  needs      0.937ms/call
done...
	
so we can get from 2.4ms/call down to 1.5ms/call with the higher bus speed! :-)
yeah thanks for this idea/tip!!!

Locked
Please be positive and constructive with your questions and comments.

Return to “Adafruit CircuitPython”