From 069a57e1b40eaedfef7b6e1bcb6906ee35aac731 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 25 Feb 2025 16:06:34 -0600 Subject: [PATCH 1/2] add qtpy s2 round display compass --- .../calibrate.py | 129 +++++++++++++ .../code.py | 170 ++++++++++++++++++ .../compass_rose.png | Bin 0 -> 10092 bytes 3 files changed, 299 insertions(+) create mode 100644 QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/calibrate.py create mode 100644 QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py create mode 100644 QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/compass_rose.png diff --git a/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/calibrate.py b/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/calibrate.py new file mode 100644 index 000000000..8629e6216 --- /dev/null +++ b/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/calibrate.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT +# +# Adapted from Liz Clark's calibrate.py in the QualiaS3 Compass learn guide +# https://learn.adafruit.com/qualia-s3-compass/code-the-compass +# +# Which was adapted from Gamblor21's calibrate.py in the Gamblor21_CircuitPython_AHRS library +# https://github.com/gamblor21/Gamblor21_CircuitPython_AHRS/blob/master/examples/calibrate.py +# +# Gyro will be calibrated first, followed by magnetometer +# Keep the board still for gyro, move around for magnetometer + +import time + +import board +from adafruit_lsm6ds.lsm6dsox import LSM6DSOX +import adafruit_lis3mdl + + +i2c = board.STEMMA_I2C() +accel_gyro = LSM6DSOX(i2c) +magnetometer = adafruit_lis3mdl.LIS3MDL(i2c) +MAG_MIN = [1000, 1000, 1000] +MAG_MAX = [-1000, -1000, -1000] + +def map_range(x, in_min, in_max, out_min, out_max): + """ + Maps a number from one range to another. + :return: Returns value mapped to new range + :rtype: float + """ + mapped = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + if out_min <= out_max: + return max(min(mapped, out_max), out_min) + + return min(max(mapped, out_max), out_min) + +def calibrate_gyro(): + """ + Calibrates gyroscope + Gyroscope values are in rads/s + """ + gx, gy, gz = accel_gyro.gyro + min_gx = gx + min_gy = gy + min_gz = gz + + max_gx = gx + max_gy = gy + max_gz = gz + + mid_gx = gx + mid_gy = gy + mid_gz = gz + + for _ in range(10): + gx, gy, gz = accel_gyro.gyro + + min_gx = min(min_gx, gx) + min_gy = min(min_gy, gy) + min_gz = min(min_gz, gz) + + max_gx = max(max_gx, gx) + max_gy = max(max_gy, gy) + max_gz = max(max_gz, gz) + + mid_gx = (max_gx + min_gx) / 2 + mid_gy = (max_gy + min_gy) / 2 + mid_gz = (max_gz + min_gz) / 2 + + print("Uncalibrated gyro: ", (gx, gy, gz)) + print("Calibrated gyro: ", (gx + mid_gx, gy + mid_gy, gz + mid_gz)) + print("Gyro calibration: ", (mid_gx, mid_gy, mid_gz)) + + time.sleep(1) + mid_gx = float(f"{mid_gx:.4f}") + mid_gy = float(f"{mid_gy:.4f}") + mid_gz = float(f"{mid_gz:.4f}") + _CAL = [mid_gx, mid_gy, mid_gz] + return _CAL + +def calibrate_mag(): + """ + Calibrates a magnometer + """ + countavg = 0 + x, y, z = magnetometer.magnetic + mag_vals = [x, y, z] + for i in range(3): + MAG_MIN[i] = min(MAG_MIN[i], mag_vals[i]) + MAG_MAX[i] = max(MAG_MAX[i], mag_vals[i]) + + for _ in range(10): + x, y, z = magnetometer.magnetic + mag_vals = [x, y, z] + + for i in range(3): + MAG_MIN[i] = min(MAG_MIN[i], mag_vals[i]) + MAG_MAX[i] = max(MAG_MAX[i], mag_vals[i]) + + countavg += 1 + print("Uncalibrated:", x, y, z) + cal_x = map_range(x, MAG_MIN[0], MAG_MAX[0], -1, 1) + cal_y = map_range(y, MAG_MIN[1], MAG_MAX[1], -1, 1) + cal_z = map_range(z, MAG_MIN[2], MAG_MAX[2], -1, 1) + print("Calibrate: ", cal_x, cal_y, cal_z) + print("MAG_MIN =", MAG_MIN) + print("MAG_MAX =", MAG_MAX) + + time.sleep(1) + return MAG_MIN, MAG_MAX + +print("Preparing gyroscope calibration. Keep board perfectly still on flat surface.") +time.sleep(5) +print("Starting gyroscope calibration..") +print() +GYRO_CAL = calibrate_gyro() +print("Gyroscope calibrated!") + +print("Preparing magnetometer calibration. Move board around in 3D space.") +time.sleep(5) +print("Starting magnetometer calibration..") +print() +MAG_MIN, MAG_MAX = calibrate_mag() +print("Magnetometer calibrated!") +print() +print("MAG_MIN =", MAG_MIN) +print("MAG_MAX =", MAG_MAX) +print("GYRO_CAL =", GYRO_CAL) diff --git a/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py b/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py new file mode 100644 index 000000000..6174ba576 --- /dev/null +++ b/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py @@ -0,0 +1,170 @@ +import math +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# SPDX-License-Identifier: MIT +# +# Adapted from QualiaS3 Compass Learn Guide by Liz Clark (Adafruit Industries) +# https://learn.adafruit.com/qualia-s3-compass/ + +import time +from math import atan2, degrees +import adafruit_lis3mdl +import board +from adafruit_lsm6ds.lsm6dsox import LSM6DSOX +from gamblor21_ahrs import mahony +import bitmaptools +from adafruit_gc9a01a import GC9A01A +import adafruit_imageload +import displayio +from fourwire import FourWire + + +# change these values to your calibration values +MAG_MIN = [-75.1973, -22.5665, -34.5221] +MAG_MAX = [-1.2131, 68.1379, 20.8126] +GYRO_CAL = [-0.0038, -0.0026, -0.0011] + + +# use filter for more accurate, but slightly slower readings +# otherwise just reads from magnetometer +ahrs = True +center_x, center_y = 120, 120 + +i2c = board.STEMMA_I2C() +accel_gyro = LSM6DSOX(i2c) +magnetometer = adafruit_lis3mdl.LIS3MDL(i2c) +# Create the AHRS filter +ahrs_filter = mahony.Mahony(50, 5, 100) + +# Variable to account for the offset between raw heading values +# and the orientation of the display. +offset_angle = 90 + + +def map_range(x, in_min, in_max, out_min, out_max): + """ + Maps a value from one range to another. + + :param x: The value to map + :param in_min: The minimum value of the input range + :param in_max: The maximum value of the input range + :param out_min: The minimum value of the output range + :param out_max: The maximum value of the output range + + :return: The value mapped to the output range + """ + mapped = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + if out_min <= out_max: + return max(min(mapped, out_max), out_min) + + return min(max(mapped, out_max), out_min) + +last_heading = offset_angle +heading = offset_angle +last_update = time.monotonic() # last time we printed the yaw/pitch/roll values +timestamp = time.monotonic_ns() # used to tune the frequency to approx 100 Hz + +# Display Setup +spi = board.SPI() +tft_cs = board.TX +tft_dc = board.RX +displayio.release_displays() +display_bus = FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=None) +display = GC9A01A(display_bus, width=240, height=240) +display.rotation = 90 + +# group to hold all of our visual elements +main_group = displayio.Group() +display.root_group = main_group + +# load the compass rose background image +rose_bmp, rose_palette = adafruit_imageload.load("compass_rose.png") +rose_tg = displayio.TileGrid(bitmap=rose_bmp, pixel_shader=rose_palette) + +# bitmap for the pointer needle +pointer = displayio.Bitmap(5, 90, 2) + +# bitmap for erasing the pointer needle +pointer_eraser = displayio.Bitmap(5, 90, 1) + +# pointer needle palette, red foreground, transparent background +pointer_palette = displayio.Palette(2) +pointer_palette[0] = 0x000000 +pointer_palette[1] = 0xFF0000 +pointer_palette.make_transparent(0) +pointer.fill(1) + +# display sized bitmap to paste the rotated needle into +rotated_pointer = displayio.Bitmap(240, 240, 2) + +# tilegrid for the rotated pointer needle +pointer_tg = displayio.TileGrid(rotated_pointer, pixel_shader=pointer_palette) + +# add rose then pointer to the displaying group +main_group.append(rose_tg) +main_group.append(pointer_tg) + +while True: + # if it's time to take a compass reading from the mag/gyro + if (time.monotonic_ns() - timestamp) > 6500000: + # read magnetic data + mx, my, mz = magnetometer.magnetic + + # map it to the calibrated values + cal_x = map_range(mx, MAG_MIN[0], MAG_MAX[0], -1, 1) + cal_y = map_range(my, MAG_MIN[1], MAG_MAX[1], -1, 1) + cal_z = map_range(mz, MAG_MIN[2], MAG_MAX[2], -1, 1) + + # if using ahrs filter + if ahrs: + # get accel/gyro data + ax, ay, az, gx, gy, gz = accel_gyro.acceleration + accel_gyro.gyro + + # apply callibration offset + gx += GYRO_CAL[0] + gy += GYRO_CAL[1] + gz += GYRO_CAL[2] + + # update filter + ahrs_filter.update(gx, gy, -gz, ax, ay, az, cal_x, -cal_y, cal_z) + + # get yaw + yaw_degree = ahrs_filter.yaw + + # convert radians to degrees + heading = degrees(yaw_degree) + + else: # not using ahrs filter + # calculate heading from calibrated mag data + # and convert from radians to degrees + heading = degrees(atan2(cal_y, cal_x)) + + # save time to compare next iteration + timestamp = time.monotonic_ns() + + # if it's time to update the display + if time.monotonic() > last_update + 0.2: + # wrap negative heading values + if heading < 0: + heading += 360 + + # if the heading is sufficiently different from previous heading + if abs(last_heading - heading) >= 2: + #print(heading) + + # erase the previous pointer needle + bitmaptools.rotozoom(rotated_pointer, pointer_eraser, + ox=120, oy=120, + px=pointer.width // 2, py=pointer.height, + angle=math.radians(last_heading + offset_angle)) + + # draw the new pointer needle + bitmaptools.rotozoom(rotated_pointer, pointer, + ox=120, oy=120, + px=pointer.width // 2, py=pointer.height, + angle=math.radians(heading + offset_angle)) + + # set the previous heading to compare next iteration + last_heading = heading + + # set the last update time to compare next iteration + last_update = time.monotonic() diff --git a/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/compass_rose.png b/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/compass_rose.png new file mode 100644 index 0000000000000000000000000000000000000000..41c782a04888176b901091e670f9ec825dce5115 GIT binary patch literal 10092 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 Date: Tue, 25 Feb 2025 16:12:38 -0600 Subject: [PATCH 2/2] fix imports --- QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py b/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py index 6174ba576..50068826c 100644 --- a/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py +++ b/QT_Py/QT_Py_ESP32_S2_RoundDisplay_Compass/code.py @@ -1,4 +1,3 @@ -import math # SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries # SPDX-License-Identifier: MIT # @@ -6,7 +5,7 @@ # https://learn.adafruit.com/qualia-s3-compass/ import time -from math import atan2, degrees +from math import atan2, degrees, radians import adafruit_lis3mdl import board from adafruit_lsm6ds.lsm6dsox import LSM6DSOX @@ -155,13 +154,13 @@ def map_range(x, in_min, in_max, out_min, out_max): bitmaptools.rotozoom(rotated_pointer, pointer_eraser, ox=120, oy=120, px=pointer.width // 2, py=pointer.height, - angle=math.radians(last_heading + offset_angle)) + angle=radians(last_heading + offset_angle)) # draw the new pointer needle bitmaptools.rotozoom(rotated_pointer, pointer, ox=120, oy=120, px=pointer.width // 2, py=pointer.height, - angle=math.radians(heading + offset_angle)) + angle=radians(heading + offset_angle)) # set the previous heading to compare next iteration last_heading = heading