from pololu_3pi_2040_robot import robot
display = robot.Display()
imu = robot.IMU()
motors = robot.Motors()
imu.reset()
imu.enable_default()
encoders = robot.Encoders()
import _thread
import time
# Your calibration data
hard_iron = [-0.953304, -0.154999, -0.537719] # offsets (hard iron)
soft_iron = [
[1.665072, -0.032199, -0.112787],
[-0.032199, 2.168006, -0.078278],
[-0.112787, -0.078278, 1.973728]
] # soft iron matrix
current_angle = None
angle_lock = _thread.allocate_lock()
def mat_vec_mul(matrix, vector):
return [
matrix[0][0]*vector[0] + matrix[0][1]*vector[1] + matrix[0][2]*vector[2],
matrix[1][0]*vector[0] + matrix[1][1]*vector[1] + matrix[1][2]*vector[2],
matrix[2][0]*vector[0] + matrix[2][1]*vector[1] + matrix[2][2]*vector[2],
]
def compensate_magnetometer(m_raw):
# Subtract hard iron offsets
corrected = [m_raw[i] - hard_iron[i] for i in range(3)]
# Apply soft iron correction matrix
compensated = mat_vec_mul(soft_iron, corrected)
return compensated
import math
class Madgwick:
def __init__(self, beta=0.1, sample_period=0.01):
self.beta = beta
self.sample_period = sample_period
self.q = [1.0, 0.0, 0.0, 0.0]
self.initial_yaw = None
def update(self, gx, gy, gz, ax, ay, az, mx, my, mz):
q1, q2, q3, q4 = self.q
norm_acc = math.sqrt(ax*ax + ay*ay + az*az)
if norm_acc == 0:
return
ax /= norm_acc
ay /= norm_acc
az /= norm_acc
norm_mag = math.sqrt(mx*mx + my*my + mz*mz)
if norm_mag == 0:
return
mx /= norm_mag
my /= norm_mag
mz /= norm_mag
# Auxiliary variables to avoid repeated arithmetic
_2q1mx = 2.0 * q1 * mx
_2q1my = 2.0 * q1 * my
_2q1mz = 2.0 * q1 * mz
_2q2mx = 2.0 * q2 * mx
# Compute reference direction of Earth's magnetic field
hx = mx * q1*q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2*q2 + 2.0 * q2 * my * q3 + 2.0 * q2 * mz * q4 - mx * q3*q3 - mx * q4*q4
hy = 2.0 * q1 * mx * q4 + my * q1*q1 - 2.0 * q1 * mz * q2 + 2.0 * q2 * mx * q3 - my * q2*q2 + my * q3*q3 + 2.0 * q3 * mz * q4 - my * q4*q4
bx = math.sqrt(hx * hx + hy * hy)
bz = -2.0 * q1 * mx * q3 + 2.0 * q1 * my * q2 + mz * q1*q1 + 2.0 * q2 * mx * q4 - mz * q2*q2 + 2.0 * q3 * my * q4 - mz * q3*q3 + mz * q4*q4
# Estimated direction of gravity and magnetic field
vx = 2.0 * (q2 * q4 - q1 * q3)
vy = 2.0 * (q1 * q2 + q3 * q4)
vz = q1*q1 - q2*q2 - q3*q3 + q4*q4
wx = 2.0 * bx * (0.5 - q3*q3 - q4*q4) + 2.0 * bz * (q2*q4 - q1*q3)
wy = 2.0 * bx * (q2*q3 - q1*q4) + 2.0 * bz * (q1*q2 + q3*q4)
wz = 2.0 * bx * (q1*q3 + q2*q4) + 2.0 * bz * (0.5 - q2*q2 - q3*q3)
# Error is sum of cross product between estimated and measured direction of gravity and magnetic field
ex = (ay * vz - az * vy) + (my * wz - mz * wy)
ey = (az * vx - ax * vz) + (mz * wx - mx * wz)
ez = (ax * vy - ay * vx) + (mx * wy - my * wx)
# Apply feedback terms
gx += self.beta * ex
gy += self.beta * ey
gz += self.beta * ez
# Integrate quaternion rate and normalize
qDot1 = -0.5 * (q2 * gx + q3 * gy + q4 * gz)
qDot2 = 0.5 * (q1 * gx + q3 * gz - q4 * gy)
qDot3 = 0.5 * (q1 * gy - q2 * gz + q4 * gx)
qDot4 = 0.5 * (q1 * gz + q2 * gy - q3 * gx)
q1 += qDot1 * self.sample_period
q2 += qDot2 * self.sample_period
q3 += qDot3 * self.sample_period
q4 += qDot4 * self.sample_period
norm_q = math.sqrt(q1*q1 + q2*q2 + q3*q3 + q4*q4)
self.q = [q1 / norm_q, q2 / norm_q, q3 / norm_q, q4 / norm_q]
def get_dps(self):
q1, q2, q3, q4 = self.q
yaw = math.atan2(2*(q1*q4 + q2*q3), 1 - 2*(q3*q3 + q4*q4))
if self.initial_yaw is None:
self.initial_yaw = yaw # Store first reading
yaw -= self.initial_yaw # Subtract offset
yaw_deg = math.degrees(yaw)
# Keep in range [-180, 180]
if yaw_deg > 180:
yaw_deg -= 360
elif yaw_deg < -180:
yaw_deg += 360
return yaw_deg
def get_yaw_wrapped(self):
q1, q2, q3, q4 = self.q
yaw = math.atan2(2*(q1*q4 + q2*q3), 1 - 2*(q3*q3 + q4*q4))
if self.initial_yaw is None:
self.initial_yaw = yaw # Store first reading
yaw -= self.initial_yaw # Subtract offset
# Convert to degrees
yaw_deg = math.degrees(-yaw)
# Wrap yaw to 0-360 clockwise
yaw_deg = yaw_deg % 360
if yaw_deg < 0:
yaw_deg += 360
return yaw_deg
madgwick = Madgwick(beta=0.05, sample_period=0.05) # set your sample period (higher to reduce drift)
def calibrate_gyro(imu, samples=200):
print("Calibrating gyroscope... Keep robot completely still.")
gx_bias = 0
gy_bias = 0
gz_bias = 0
for i in range(samples):
imu.read()
gx, gy, gz = imu.gyro.last_reading_dps
gx_bias += gx
gy_bias += gy
gz_bias += gz
time.sleep(0.01) # 10 ms delay between samples
gx_bias /= samples
gy_bias /= samples
gz_bias /= samples
print("Gyro bias (dps):", gx_bias, gy_bias, gz_bias)
return gx_bias, gy_bias, gz_bias
programFinished = False
last_time = time.ticks_ms()
calibrated = False
gx_bias = 0
gy_bias = 0
gz_bias = 0
if not calibrated:
gx_bias, gy_bias, gz_bias = calibrate_gyro(imu)
calibrated = True
def angle_thread():
global current_angle
last_time = time.ticks_ms()
while True:
imu.read()
# Get time difference for Madgwick update
now = time.ticks_ms()
dt = time.ticks_diff(now, last_time) / 1000.0
last_time = now
# Read sensors
gx, gy, gz = imu.gyro.last_reading_dps
ax, ay, az = imu.acc.last_reading_g
raw_mag = imu.mag.last_reading_gauss
# Normalize and compensate magnetometer as before (your code)
gx, gy, gz = imu.gyro.last_reading_dps
gx = (gx - gx_bias) * (math.pi /180)
gy = (gy - gy_bias) * (math.pi /180)
gz = (gz - gz_bias) * (math.pi /180)
ax, ay, az = imu.acc.last_reading_g
norm = math.sqrt(ax**2 + ay**2 + az**2)
ax /= norm
ay /= norm
az /= norm
calibrated_mag = compensate_magnetometer(raw_mag)
mx, my, mz = calibrated_mag
norm = math.sqrt(mx**2 + my**2 + mz**2)
mx /= norm
my /= norm
mz /= norm
# Update Madgwick filter
madgwick.sample_period = dt
madgwick.update(gx, gy, gz, ax, ay, az, mx, my, mz)
# Get the yaw angle
angle = madgwick.get_yaw_wrapped()
# Store it thread-safely
with angle_lock:
current_angle = angle
time.sleep(0.01) # Adjust delay as needed
if not calibrated:
gx_bias, gy_bias, gz_bias = calibrate_gyro(imu)
calibrated = True
_thread.start_new_thread(angle_thread, ())
while not programFinished:
with angle_lock:
angle = current_angle
if angle is not None:
display.text("angle:", 0, 0)
print(angle)
# Example: call move_forward at some point or based on angle
# move_forward(500)
time.sleep(0.1) # Main loop delay
while not programFinished:
imu.read()
raw_mag = imu.mag.last_reading_gauss
gx, gy, gz = imu.gyro.last_reading_dps
gx = (gx - gx_bias) * (math.pi /180)
gy = (gy - gy_bias) * (math.pi /180)
gz = (gz - gz_bias) * (math.pi /180)
ax, ay, az = imu.acc.last_reading_g
norm = math.sqrt(ax**2 + ay**2 + az**2)
ax /= norm
ay /= norm
az /= norm
calibrated_mag = compensate_magnetometer(raw_mag)
mx, my, mz = calibrated_mag
norm = math.sqrt(mx**2 + my**2 + mz**2)
mx /= norm
my /= norm
mz /= norm
now = time.ticks_ms()
dt = time.ticks_diff(now, last_time) / 1000.0
last_time = now
madgwick.sample_period = dt
display.fill(0)
madgwick.update(gx, gy, gz, ax, ay, az, mx, my, mz)
currDps = (madgwick.get_yaw_wrapped())
display.text("this:", 0, 0)
display.text(str(currDps), 0, 10)
display.show()