Skip to content

Commit ede1dd8

Browse files
committed
adding compass project files
1 parent f08869e commit ede1dd8

File tree

4 files changed

+321
-0
lines changed

4 files changed

+321
-0
lines changed
345 KB
Binary file not shown.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Adapted from Gamblor21's calibrate.py in the Gamblor21_CircuitPython_AHRS library
5+
# https://github.com/gamblor21/Gamblor21_CircuitPython_AHRS/blob/master/examples/calibrate.py
6+
#
7+
# Gyro will be calibrated first, followed by magnetometer
8+
# Keep the board still for gyro, move around for magnetometer
9+
10+
import time
11+
from adafruit_lsm6ds.lsm6dsox import LSM6DSOX
12+
import adafruit_lis3mdl
13+
from adafruit_qualia.graphics import Graphics, Displays
14+
15+
graphics = Graphics(Displays.ROUND21, default_bg=None, auto_refresh=True)
16+
i2c = graphics.i2c_bus
17+
accel_gyro = LSM6DSOX(i2c)
18+
magnetometer = adafruit_lis3mdl.LIS3MDL(i2c)
19+
MAG_MIN = [1000, 1000, 1000]
20+
MAG_MAX = [-1000, -1000, -1000]
21+
22+
def map_range(x, in_min, in_max, out_min, out_max):
23+
"""
24+
Maps a number from one range to another.
25+
:return: Returns value mapped to new range
26+
:rtype: float
27+
"""
28+
mapped = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
29+
if out_min <= out_max:
30+
return max(min(mapped, out_max), out_min)
31+
32+
return min(max(mapped, out_max), out_min)
33+
34+
def calibrate_gyro():
35+
"""
36+
Calibrates gyroscope
37+
Gyroscope values are in rads/s
38+
"""
39+
gx, gy, gz = accel_gyro.gyro
40+
min_gx = gx
41+
min_gy = gy
42+
min_gz = gz
43+
44+
max_gx = gx
45+
max_gy = gy
46+
max_gz = gz
47+
48+
mid_gx = gx
49+
mid_gy = gy
50+
mid_gz = gz
51+
52+
for _ in range(10):
53+
gx, gy, gz = accel_gyro.gyro
54+
55+
min_gx = min(min_gx, gx)
56+
min_gy = min(min_gy, gy)
57+
min_gz = min(min_gz, gz)
58+
59+
max_gx = max(max_gx, gx)
60+
max_gy = max(max_gy, gy)
61+
max_gz = max(max_gz, gz)
62+
63+
mid_gx = (max_gx + min_gx) / 2
64+
mid_gy = (max_gy + min_gy) / 2
65+
mid_gz = (max_gz + min_gz) / 2
66+
67+
print("Uncalibrated gyro: ", (gx, gy, gz))
68+
print("Calibrated gyro: ", (gx + mid_gx, gy + mid_gy, gz + mid_gz))
69+
print("Gyro calibration: ", (mid_gx, mid_gy, mid_gz))
70+
71+
time.sleep(1)
72+
mid_gx = float(f"{mid_gx:.4f}")
73+
mid_gy = float(f"{mid_gy:.4f}")
74+
mid_gz = float(f"{mid_gz:.4f}")
75+
_CAL = [mid_gx, mid_gy, mid_gz]
76+
return _CAL
77+
78+
def calibrate_mag():
79+
"""
80+
Calibrates a magnometer
81+
"""
82+
countavg = 0
83+
x, y, z = magnetometer.magnetic
84+
mag_vals = [x, y, z]
85+
for i in range(3):
86+
MAG_MIN[i] = min(MAG_MIN[i], mag_vals[i])
87+
MAG_MAX[i] = max(MAG_MAX[i], mag_vals[i])
88+
89+
for _ in range(10):
90+
x, y, z = magnetometer.magnetic
91+
mag_vals = [x, y, z]
92+
93+
for i in range(3):
94+
MAG_MIN[i] = min(MAG_MIN[i], mag_vals[i])
95+
MAG_MAX[i] = max(MAG_MAX[i], mag_vals[i])
96+
97+
countavg += 1
98+
print("Uncalibrated:", x, y, z)
99+
cal_x = map_range(x, MAG_MIN[0], MAG_MAX[0], -1, 1)
100+
cal_y = map_range(y, MAG_MIN[1], MAG_MAX[1], -1, 1)
101+
cal_z = map_range(z, MAG_MIN[2], MAG_MAX[2], -1, 1)
102+
print("Calibrated: ", cal_x, cal_y, cal_z)
103+
print("MAG_MIN =", MAG_MIN)
104+
print("MAG_MAX =", MAG_MAX)
105+
106+
time.sleep(1)
107+
return MAG_MIN, MAG_MAX
108+
109+
print("Preparing gyroscope calibration. Keep board perfectly still on flat surface.")
110+
time.sleep(5)
111+
print("Starting gyroscope calibration..")
112+
print()
113+
GYRO_CAL = calibrate_gyro()
114+
print("Gyroscope calibrated!")
115+
116+
print("Preparing magnetometer calibration. Move board around in 3D space.")
117+
time.sleep(5)
118+
print("Starting magnetometer calibration..")
119+
print()
120+
MAG_MIN, MAG_MAX = calibrate_mag()
121+
print("Magnetometer calibrated!")
122+
print()
123+
print("MAG_MIN =", MAG_MIN)
124+
print("MAG_MAX =", MAG_MAX)
125+
print("GYRO_CAL =", GYRO_CAL)

Qualia/Qualia_S3_Compass/code.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
# Written by Liz Clark (Adafruit Industries)
4+
# with OpenAI ChatGPT v4 Jan 10, 2024 build
5+
# https://help.openai.com/en/articles/6825453-chatgpt-release-notes
6+
7+
# https://chat.openai.com/share/19de8f24-3191-43b8-a9c9-95f1b8000e80
8+
9+
import time
10+
from math import atan2, degrees, cos, sin, radians
11+
import adafruit_lis3mdl
12+
import vectorio
13+
import displayio
14+
from adafruit_display_text import bitmap_label
15+
from adafruit_bitmap_font import bitmap_font
16+
from adafruit_qualia.graphics import Graphics, Displays
17+
from adafruit_lsm6ds.lsm6dsox import LSM6DSOX
18+
from gamblor21_ahrs import mahony
19+
import bitmaptools
20+
from jpegio import JpegDecoder
21+
# change these values to your calibration values
22+
MAG_MIN = [-11.5902, -47.1353, -28.7635]
23+
MAG_MAX = [79.7866, 48.0854, 63.461]
24+
GYRO_CAL = [-7.3934, -0.000100605, 2.7703]
25+
# use filter for more accurate, but slightly slower readings
26+
# otherwise just reads from magnetometer
27+
ahrs = True
28+
center_x, center_y = 240, 240
29+
30+
graphics = Graphics(Displays.ROUND21, default_bg=None, auto_refresh=False)
31+
32+
i2c = graphics.i2c_bus
33+
accel_gyro = LSM6DSOX(i2c)
34+
magnetometer = adafruit_lis3mdl.LIS3MDL(i2c)
35+
# Create the AHRS filter
36+
ahrs_filter = mahony.Mahony(50, 5, 100)
37+
38+
group = displayio.Group()
39+
# palette for vectorio graphics
40+
pointer_pal = displayio.Palette(5)
41+
pointer_pal[0] = 0xFFFF00
42+
pointer_pal[1] = 0x000000
43+
pointer_pal[2] = 0xFFFFFF
44+
pointer_pal[3] = 0xFF0000
45+
pointer_pal[4] = 0x0000FF
46+
pointer_pal.make_transparent(0)
47+
# compass image is a jpeg
48+
decoder = JpegDecoder()
49+
width, height = decoder.open("/compass.jpg")
50+
bitmap_compass = displayio.Bitmap(width, height, 20)
51+
palette_compass = displayio.ColorConverter(input_colorspace = displayio.Colorspace.RGB565_SWAPPED)
52+
decoder.decode(bitmap_compass)
53+
# blank bitmap for rotozoom
54+
compass_blank = displayio.Bitmap(width, height, 1)
55+
# carrier bitmap for compass for rotozoom
56+
compass_scribble = displayio.Bitmap(width, height, 20)
57+
tile_grid = displayio.TileGrid(compass_scribble, pixel_shader=palette_compass)
58+
# only tilegrid is added to group
59+
group.append(tile_grid)
60+
61+
radius = center_x
62+
angle = 225
63+
rad_angle = radians(angle)
64+
65+
# place small circle to denote heading direction from 9-DoF relative to display
66+
circle_radius = 5
67+
header_angle = radians(135)
68+
edge_x = center_x + radius * cos(header_angle)
69+
edge_y = center_y + radius * sin(header_angle)
70+
adjusted_x = edge_x - circle_radius * cos(header_angle)
71+
adjusted_y = edge_y - circle_radius * sin(header_angle)
72+
header = vectorio.Circle(pixel_shader=pointer_pal, color_index = 2,
73+
radius=circle_radius, x=int(adjusted_x), y=int(adjusted_y))
74+
75+
center = vectorio.Circle(pixel_shader=pointer_pal, color_index = 2, radius=50, x=240, y=240)
76+
77+
font_file = "/Roboto-Regular-47.pcf"
78+
font = bitmap_font.load_font(font_file)
79+
80+
direction_text = bitmap_label.Label(font, text="000", color=None)
81+
direction_text.x = center.x - 40
82+
direction_text.y = center.y
83+
direction_bitmap = direction_text.bitmap
84+
85+
direction_blank = displayio.Bitmap(direction_text.bounding_box[2],
86+
direction_text.bounding_box[2], 1)
87+
direction_scribble = displayio.Bitmap(direction_text.bounding_box[2],
88+
direction_text.bounding_box[2], len(pointer_pal))
89+
direction_grid = displayio.TileGrid(direction_scribble, pixel_shader=pointer_pal, x=200, y=200)
90+
91+
def create_line_of_squares(l, n, color):
92+
squares = []
93+
square_size = l // n
94+
for i in range(n):
95+
x = center_x + i * square_size
96+
y = center_y - square_size // 2
97+
square_points = [(0, 0), (square_size, 0),
98+
(square_size, square_size), (0, square_size)]
99+
square = vectorio.Polygon(pixel_shader=pointer_pal,
100+
color_index=color, points=square_points, x=x, y=y)
101+
group.append(square)
102+
squares.append(square)
103+
return squares
104+
105+
def update_line_of_squares(squares, h, l):
106+
r = radians(-h)
107+
n = len(squares)
108+
square_size = l // n
109+
x = center_x - (square_size - 2)
110+
y = center_y - (square_size - 2)
111+
for i, square in enumerate(squares):
112+
offset_x = x + i * square_size - x
113+
offset_y = -square_size // 2
114+
rotated_x = offset_x * cos(r) - offset_y * sin(r)
115+
rotated_y = offset_x * sin(r) + offset_y * cos(r)
116+
square.x = int(x + rotated_x)
117+
square.y = int(y + rotated_y)
118+
119+
length = 200 # Length of the lines
120+
num_squares = 20 # Number of squares for each line
121+
122+
vertical_squares = create_line_of_squares(length, num_squares, 3)
123+
update_line_of_squares(vertical_squares, angle, length)
124+
125+
def map_range(x, in_min, in_max, out_min, out_max):
126+
mapped = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
127+
if out_min <= out_max:
128+
return max(min(mapped, out_max), out_min)
129+
130+
return min(max(mapped, out_max), out_min)
131+
132+
group.append(center)
133+
group.append(header)
134+
group.append(direction_text)
135+
group.append(direction_grid)
136+
137+
graphics.display.root_group = group
138+
139+
last_heading = angle
140+
heading = angle
141+
last_update = time.monotonic() # last time we printed the yaw/pitch/roll values
142+
timestamp = time.monotonic_ns() # used to tune the frequency to approx 100 Hz
143+
spin_rose = True
144+
145+
graphics.display.refresh()
146+
147+
while True:
148+
if graphics.touch.touched:
149+
spin_rose = not spin_rose
150+
# reset last_heading to trigger an update
151+
last_heading = heading + 5
152+
if spin_rose:
153+
direction_text.color = None
154+
update_line_of_squares(vertical_squares, angle, length)
155+
else:
156+
bitmaptools.rotozoom(compass_scribble, bitmap_compass, angle = radians(0))
157+
direction_text.color = pointer_pal[1]
158+
graphics.display.refresh()
159+
# touch debounce delay
160+
time.sleep(0.2)
161+
if (time.monotonic_ns() - timestamp) > 6500000:
162+
mx, my, mz = magnetometer.magnetic
163+
cal_x = map_range(mx, MAG_MIN[0], MAG_MAX[0], -1, 1)
164+
cal_y = map_range(my, MAG_MIN[1], MAG_MAX[1], -1, 1)
165+
cal_z = map_range(mz, MAG_MIN[2], MAG_MAX[2], -1, 1)
166+
if ahrs:
167+
ax, ay, az, gx, gy, gz = accel_gyro.acceleration + accel_gyro.gyro
168+
gx += GYRO_CAL[0]
169+
gy += GYRO_CAL[1]
170+
gz += GYRO_CAL[2]
171+
ahrs_filter.update(gx, gy, -gz, ax, ay, az, cal_x, -cal_y, cal_z)
172+
yaw_degree = ahrs_filter.yaw
173+
heading = degrees(yaw_degree)
174+
else:
175+
heading = degrees(atan2(cal_y, cal_x))
176+
timestamp = time.monotonic_ns()
177+
if time.monotonic() > last_update + 0.2:
178+
if heading < 0:
179+
heading += 360
180+
if abs(last_heading - heading) >= 2:
181+
direction_text.text = str(int(heading))
182+
if spin_rose:
183+
direction_bitmap = direction_text.bitmap
184+
185+
bitmaptools.rotozoom(compass_scribble, bitmap_compass,
186+
angle = radians(-heading+angle))
187+
bitmaptools.rotozoom(direction_scribble, direction_bitmap, angle = rad_angle)
188+
graphics.display.refresh()
189+
bitmaptools.rotozoom(direction_scribble, direction_blank, angle = rad_angle)
190+
bitmaptools.rotozoom(compass_scribble, compass_blank,
191+
angle = radians(-heading+angle))
192+
else:
193+
update_line_of_squares(vertical_squares, -heading + 90, length)
194+
graphics.display.refresh()
195+
last_heading = heading
196+
last_update = time.monotonic()

Qualia/Qualia_S3_Compass/compass.jpg

48 KB
Loading

0 commit comments

Comments
 (0)