Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pbio/imu: Implement 3D attitude and fusion of IMU data. #270

Merged
merged 20 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4ddd2d8
pbio/imu: Drop redundant struct nesting.
laurensvalk Oct 17, 2024
f79af7f
.vscode: Add task for deploying Prime Hub.
laurensvalk Oct 19, 2024
6006d2b
pbio/sys/storage: Refactor IMU settings to IMU module.
laurensvalk Oct 19, 2024
9c75126
pbio/imu: Refactor settings setters with flags.
laurensvalk Oct 21, 2024
60759b4
pbio/imu: Add setters for gravity offsets.
laurensvalk Oct 21, 2024
f00e345
pbio/imu: Calibrate acceleration values.
laurensvalk Oct 21, 2024
60273d6
pybricks.common.IMU: Fix failing to raise on bad axes.
laurensvalk Oct 21, 2024
4fa2174
pybricks/util_mp: Unify all args None test.
laurensvalk Oct 21, 2024
15c84e7
pybricks/util_mp: Use args None test.
laurensvalk Oct 21, 2024
9388ddb
pbio/imu: Set calibrated gyro values.
laurensvalk Oct 21, 2024
216a0b6
pbio/imu: Implement 3D fusion.
laurensvalk Oct 24, 2024
30974de
pbio/imu: Save one off bias calibration.
laurensvalk Oct 25, 2024
037543d
pbio/drv/imu_lsm6ds3: Start stationary detection from slow average.
laurensvalk Oct 29, 2024
accb117
pbio/imu: Reduce default gyro threshold to 2deg/s.
laurensvalk Oct 29, 2024
967a4d7
pbio/imu: Make 3D heading optional.
laurensvalk Dec 2, 2024
8dc43f3
pbio/imu: Allow reading uncalibrated rotation.
laurensvalk Dec 3, 2024
442d75a
bricks: Use own manifest file.
laurensvalk Dec 3, 2024
d7fb426
pbio/imu: Also apply 1D correction to final 3D result.
laurensvalk Dec 3, 2024
53b3c20
bricks: Freeze only specific files.
laurensvalk Dec 3, 2024
77eb752
bricks/primehub/modules: Add calibration routine.
laurensvalk Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@
"type": "shell",
"command": "make movehub -j"
},
{
"label": "build and deploy primehub",
"type": "shell",
"command": "make",
"args": ["-j", "deploy"],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}/bricks/primehub"
},
"problemMatcher": [
{
"owner": "cpp",
"fileLocation": ["relative", "${workspaceFolder}/bricks/primehub"],
"pattern": {
"regexp": "^(.*?):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
],
"detail": "Build the PrimeHub project"
},
{
"label": "build virtualhub",
"type": "shell",
Expand Down
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,33 @@

## [Unreleased]

### Added

- Added optional `calibrated=True` parameter to `acceleration()` and `up()` and
`angular_velocity()` methods of the IMU ([support#943]).
- Implemented `hub.imu.orientation()` to give the rotation matrix of the hub or
robot with respect to the inertial frame.
- Added calibration parameters that can be set for angular velocity offset and
scale and acceleration offset and scale.

### Changed

- The method `DriveBase.angle()` now returns a float ([support#1844]). This
makes it properly equivalent to `hub.imu.heading`.
- Re-implemented tilt using the gyro data by default. Pure accelerometer tilt
can still be obtained with `hub.imu.tilt(use_gyro=False)`.
- Re-implemented `hub.imu.heading()` to use optionally use the projection of 3D
orientation to improve performance when the hub is lifted off the ground.
The 1D-based heading remains the default for now.

### Fixed
- Fixed `DriveBase.angle()` getting an incorrectly rounded gyro value, which
could cause `turn(360)` to be off by a degree ([support#1844]).
- Fixed `hub` silently ignoring non-orthogonal base axis when it should raise.

[support#1844]: https://github.com/pybricks/support/issues/1844
[support#943]: https://github.com/pybricks/support/issues/943
[support#1886]: https://github.com/pybricks/support/issues/1886
[support#1844]: https://github.com/pybricks/support/issues/1844

## [3.6.0b2] - 2024-10-15

Expand Down
4 changes: 1 addition & 3 deletions bricks/_common/arm_none_eabi.mk
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,7 @@ include $(PBTOP)/micropython/py/mkenv.mk

# Include common frozen modules.
ifeq ($(PB_FROZEN_MODULES),1)
ifneq ("$(wildcard $(PBTOP)/bricks/_common/modules/*.py)","")
FROZEN_MANIFEST ?= ../_common/manifest.py
endif
FROZEN_MANIFEST ?= manifest.py
endif

# qstr definitions (must come before including py.mk)
Expand Down
7 changes: 2 additions & 5 deletions bricks/_common/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,5 @@

modules = list(pathlib.Path("./modules").glob("*.py"))

if any(modules):
for m in modules:
path, file = m.parts
print(f"Including {m.stem} as a module.")
freeze_as_mpy(path, file)
for m in modules:
freeze_as_mpy(str(m.parent), m.name)
1 change: 0 additions & 1 deletion bricks/_common/modules/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
*.py
!_hub_extra.py
!_builtin_port_view.py
115 changes: 0 additions & 115 deletions bricks/_common/modules/_hub_extra.py

This file was deleted.

1 change: 1 addition & 0 deletions bricks/cityhub/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include("../_common/manifest.py")
1 change: 1 addition & 0 deletions bricks/debug/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include("../_common/manifest.py")
5 changes: 5 additions & 0 deletions bricks/essentialhub/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Common modules
include("../_common/manifest.py")

# Essential Hub IMU is the same as the one in Prime Hub
freeze_as_mpy("../primehub/modules", "_imu_calibrate.py")
1 change: 1 addition & 0 deletions bricks/ev3/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include("../_common/manifest.py")
1 change: 1 addition & 0 deletions bricks/nxt/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include("../_common/manifest.py")
5 changes: 5 additions & 0 deletions bricks/primehub/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Common modules
include("../_common/manifest.py")

freeze_as_mpy("../primehub/modules", "_imu_calibrate.py")
freeze_as_mpy("../primehub/modules", "_light_matrix.py")
149 changes: 149 additions & 0 deletions bricks/primehub/modules/_imu_calibrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
from pybricks.hubs import ThisHub
from pybricks.parameters import Side, Axis
from pybricks.tools import wait, vector


hub = ThisHub()


def beep(freq):
try:
hub.speaker.beep(freq, 100)
except AttributeError:
# Technic hub does not have a speaker.
pass
wait(10)


def wait_for_stationary(side):
while not hub.imu.stationary() or hub.imu.up() != side:
wait(10)


up_sides = {
Side.FRONT: (0, 0),
Side.BACK: (1, 0),
Side.LEFT: (2, 1),
Side.RIGHT: (3, 1),
Side.TOP: (4, 2),
Side.BOTTOM: (5, 2),
}

gravity = [0] * 6
bias = vector(0, 0, 0)

STATIONARY_COUNT = 1000


def roll_over_axis(axis, new_side):

global bias, bias_count

print("Roll it towards you, without lifting the hub up!")

angle_start = hub.imu.rotation(axis, calibrated=False)
while hub.imu.up() != new_side or not hub.imu.stationary():

_, _, z = hub.imu.orientation() * axis
if abs(z) > 0.07:
print(hub.imu.orientation() * axis)
raise RuntimeError("Lifted it!")
wait(100)

uncalibrated_90_deg_rotation = abs(hub.imu.rotation(axis, calibrated=False) - angle_start)
if abs(uncalibrated_90_deg_rotation - 90) > 10:
raise RuntimeError("Not 90 deg!")

print("Calibrating...")
beep(1000)

rotation_start = vector(
hub.imu.rotation(Axis.X, calibrated=False),
hub.imu.rotation(Axis.Y, calibrated=False),
hub.imu.rotation(Axis.Z, calibrated=False),
)

acceleration = vector(0, 0, 0)

for i in range(STATIONARY_COUNT):
acceleration += hub.imu.acceleration(calibrated=False)
bias += hub.imu.angular_velocity(calibrated=False)
wait(1)

acceleration /= STATIONARY_COUNT

rotation_end = vector(
hub.imu.rotation(Axis.X, calibrated=False),
hub.imu.rotation(Axis.Y, calibrated=False),
hub.imu.rotation(Axis.Z, calibrated=False),
)
if abs(rotation_end - rotation_start) > 1:
raise RuntimeError("Moved it!")

side_index, axis_index = up_sides[new_side]

# Store the gravity value for the current side being up.
# We will visit each side several times. We'll divide by the number
# of visits later.
gravity[side_index] += acceleration[axis_index]

beep(500)

return uncalibrated_90_deg_rotation


calibrate_x = """
Going to calibrate X now!
- Put the hub on the table in front of you.
- top side (display) facing up
- right side (ports BDF) towards you.
"""

calibrate_y = """
Going to calibrate Y now
- Put the hub on the table in front of you.
- top side (display) facing up
- back side (speaker) towards you.
"""

calibrate_z = """
Going to calibrate Z now!
- Put the hub on the table in front of you.
- front side (USB port) facing up
- left side (ports ACE) towards you
"""

REPEAT = 2

# For each 3-axis run, we will visit each side twice.
SIDE_COUNT = REPEAT * 2


def roll_hub(axis, message, start_side, sides):
print(message)
wait_for_stationary(start_side)
beep()
rotation = 0
for _ in range(REPEAT):
for side in sides:
rotation += roll_over_axis(axis, side)
return rotation / REPEAT


rotation_x = roll_hub(
Axis.X, calibrate_x, Side.TOP, [Side.LEFT, Side.BOTTOM, Side.RIGHT, Side.TOP]
)
rotation_y = roll_hub(
Axis.Y, calibrate_y, Side.TOP, [Side.FRONT, Side.BOTTOM, Side.BACK, Side.TOP]
)
rotation_z = roll_hub(
Axis.Z, calibrate_z, Side.FRONT, [Side.RIGHT, Side.BACK, Side.LEFT, Side.FRONT]
)

hub.imu.settings(
angular_velocity_bias=tuple(bias / SIDE_COUNT / STATIONARY_COUNT / 6),
angular_velocity_scale=(rotation_x, rotation_y, rotation_z),
acceleration_correction=[g / SIDE_COUNT for g in gravity],
)

print("Result: ", hub.imu.settings())
9 changes: 9 additions & 0 deletions bricks/primehub/modules/_light_matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pybricks.tools import wait


def light_matrix_text_async(display, text, on, off):
for char in text:
display.char(char)
yield from wait(on)
display.off()
yield from wait(off)
5 changes: 5 additions & 0 deletions bricks/technichub/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Common modules
include("../_common/manifest.py")

# Technic Hub IMU is the same as the one in Prime Hub
freeze_as_mpy("../primehub/modules", "_imu_calibrate.py")
Loading
Loading