class Q: HOST = "localhost" PORT = 4223 UID = "aeoUQwwyAvY" # Change to your UID def __init__(self): self.base_x = 0.0 self.base_y = 0.0 self.base_z = 0.0 self.base_w = 0.0 self.imu = IMU(self.UID) # Create device object self.ipcon = IPConnection(self.HOST, self.PORT) # Create IPconnection to brickd self.ipcon.add_device(self.imu) # Add device to IP connection # Don't use device before it is added to a connection # Wait for IMU to settle print 'Set IMU to base position and wait for 10 seconds' print 'Base position will be 0 for all angles' time.sleep(10) q = self.imu.get_quaternion() self.set_base_coordinates(q.x, q.y, q.z, q.w) # Set period for quaternion callback to 10ms self.imu.set_quaternion_period(10) # Register quaternion callback self.imu.register_callback(self.imu.CALLBACK_QUATERNION, self.quaternion_cb) def quaternion_cb(self, x, y, z, w): # Use conjugate of quaternion to rotate coordinates according to base system x, y, z, w = self.make_relative_coordinates(-x, -y, -z, w) x_angle = int(math.atan2(2.0*(y*z - w*x), 1.0 - 2.0*(x*x + y*y))*180/math.pi) y_angle = int(math.atan2(2.0*(x*z + w*y), 1.0 - 2.0*(x*x + y*y))*180/math.pi) z_angle = int(math.atan2(2.0*(x*y + w*z), 1.0 - 2.0*(x*x + z*z))*180/math.pi) print 'x: {0}, y: {1}, z: {2}'.format(x_angle, y_angle, z_angle) def set_base_coordinates(self, x, y, z, w): self.base_x = x self.base_y = y self.base_z = z self.base_w = w def make_relative_coordinates(self, x, y, z, w): # Multiply base quaternion with current quaternion return ( w * self.base_x + x * self.base_w + y * self.base_z - z * self.base_y, w * self.base_y - x * self.base_z + y * self.base_w + z * self.base_x, w * self.base_z + x * self.base_y - y * self.base_x + z * self.base_w, w * self.base_w - x * self.base_x - y * self.base_y - z * self.base_z )
class IMUBarometerFusion(QMainWindow): FACTOR = 1 KP1 = 0.55 * FACTOR # PI observer velocity gain KP2 = 1.0 * FACTOR # PI observer position gain KI = 0.001 / FACTOR # PI observer integral gain (bias cancellation) def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.initialized = False self.altitude_error_i = 0 self.acc_scale = 0.0 self.start_time = time.time() self.altitude_error = 0 self.inst_acceleration = 0.0 self.delta = 0 self.estimated_velocity = 0.0 self.estimated_altitude = 0.0 self.last_time = time.time() self.last_orig_altitude = 0 self.last_estimated_altitude = 0 ipcon = IPConnection() self.imu = IMU(UID_IMU, ipcon) self.barometer = Barometer(UID_BAROMETER, ipcon) ipcon.connect(HOST, PORT) # Turn leds and orientation calculation off, to save calculation time # for the IMU Brick. This makes sure that the measurements are taken # in equidistant 2ms intervals self.imu.leds_off() self.imu.orientation_calculation_off() # Turn averaging of in the Barometer Bricklet to make sure that # the data is without delay self.barometer.set_averaging(0, 0, 0) red_pen = QPen(Qt.red) red_pen.setWidth(5) plot_list = [ #['', Qt.blue, self.get_orig_value], ['', red_pen, self.get_estimated_value] ] self.plot_widget = PlotWidget('Height [m]', plot_list) self.plot_widget.stop = False self.setCentralWidget(self.plot_widget) self.timer = QTimer() self.timer.timeout.connect(self.update) self.timer.start(6) def get_orig_value(self): return self.last_orig_altitude def get_estimated_value(self): return self.last_estimated_altitude # Update measurements and compute new altitude every 6ms. def update(self): q = self.imu.get_quaternion() acc = self.imu.get_acceleration() alt = self.barometer.get_altitude() / 100.0 compensated_acc_q = self.compute_compensated_acc(q, acc) compensated_acc_q_earth = self.compute_dynamic_acceleration_vector( q, compensated_acc_q) self.last_orig_altitude = alt self.last_estimated_altitude = self.compute_altitude( compensated_acc_q_earth[2], alt) # Remove gravity from accelerometer measurements def compute_compensated_acc(self, q, a): g = (2 * (q.x * q.z - q.w * q.y), 2 * (q.w * q.x + q.y * q.z), q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z) return (a[0] / 1000.0 - g[0], a[1] / 1000.0 - g[1], a[2] / 1000.0 - g[2], 0.0) # Rotate dynamic acceleration vector from sensor frame to earth frame def compute_dynamic_acceleration_vector(self, q, compensated_acc_q): def q_conj(q): return -q[0], -q[1], -q[2], q[3] def q_mult(q1, q2): x1, y1, z1, w1 = q1 x2, y2, z2, w2 = q2 w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2 z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 return x, y, z, w tmp = q_mult(q, compensated_acc_q) return q_mult(tmp, q_conj(q)) # Computes estimation of altitude based on barometer and accelerometer measurements # Code is based on blog post from Fabio Varesano: http://www.varesano.net/blog/fabio/complementary-filtering-high-res-barometer-and-accelerometer-reliable-altitude-estimation # He seems to have got the idea from the MultiWii project def compute_altitude(self, compensated_acceleration, altitude): self.current_time = time.time() # Initialization if not self.initialized: self.initialized = True self.estimated_altitude = altitude self.estimated_velocity = 0 self.altitude_error_i = 0 # Estimation Error self.altitude_error = altitude - self.estimated_altitude self.altitude_error_i = self.altitude_error_i + self.altitude_error self.altitude_error_i = min(2500.0, max(-2500.0, self.altitude_error_i)) self.inst_acceleration = compensated_acceleration * 9.80665 + self.altitude_error_i * self.KI dt = self.current_time - self.last_time # Integrators self.delta = self.inst_acceleration * dt + (self.KP1 * dt) * self.altitude_error self.estimated_altitude += (self.estimated_velocity / 5.0 + self.delta) * (dt / 2) + ( self.KP2 * dt) * self.altitude_error self.estimated_velocity += self.delta * 10.0 self.last_time = self.current_time return self.estimated_altitude
class TomIMU: def __init__(self,host,port,uid,callbackPeriodMS=100): self.host=host self.port=port self.uid=uid self._imu = IMU(uid) # Create device object self._ipcon = IPConnection(self.host,self.port) # Create IPconnection to brickd self._ipcon.add_device(self._imu) # Add device to IP connection self.ready = True # Don't use device before it is added to a connection # Set period for quaternion callback (defaults to 100ms) self._imu.set_quaternion_period(callbackPeriodMS) # Register quaternion callback self._imu.register_callback(self._imu.CALLBACK_QUATERNION, self._QuaternionCallback) self._imu.leds_off() # Turn LEDs off. self._imu.set_convergence_speed(5) # 5ms convergence. # Orientation origin and most recent values q = self._imu.get_quaternion() # Get a temp quaternion from current pose. self.rel_x = q.x self.rel_y = q.y self.rel_z = q.z self.rel_w = q.w self.x = q.x self.y = q.y self.z = q.z self.w = q.w def __destroy__(self): self._ipcon.destroy() def _QuaternionCallback(self, x, y, z, w): """ Records the most recent quaternion orientation values. """ self.x,self.y,self.z,self.w = x,y,z,w def SetOrientationOrigin(self, origin=None): """ Resets the orientation origin to the given values, or the latest reading if none. """ if origin is None: self.rel_x, self.rel_y, self.rel_z, self.rel_w = self.x, self.y, self.z, self.w else: self.rel_x, self.rel_y, self.rel_z, self.rel_w = origin def GetEulerOrientation(self): x,y,z,w = self.GetQuaternionOrientation() from math import atan2, asin roll = atan2(2.0*y*w - 2.0*x*z, 1.0 - 2.0*y*y - 2.0*z*z) pitch = atan2(2.0*x*w - 2.0*y*z, 1.0 - 2.0*x*x - 2.0*z*z) yaw = asin(2.0*x*y + 2.0*z*w) return roll,pitch,yaw def GetQuaternionOrientation(self): # Conjugate x,y,z = -self.x, -self.y, -self.z w = self.w # Multiply wn = w * self.rel_w - x * self.rel_x - y * self.rel_y - z * self.rel_z xn = w * self.rel_x + x * self.rel_w + y * self.rel_z - z * self.rel_y yn = w * self.rel_y - x * self.rel_z + y * self.rel_w + z * self.rel_x zn = w * self.rel_z + x * self.rel_y - y * self.rel_x + z * self.rel_w return xn,yn,zn,wn def GetConvergenceSpeed(self): return self._imu.get_convergence_speed() def SetConvergenceSpeed(self, speed): self._imu.set_convergence_speed(speed)
class IMUBarometerFusion(QMainWindow): FACTOR = 1 KP1 = 0.55*FACTOR # PI observer velocity gain KP2 = 1.0*FACTOR # PI observer position gain KI = 0.001/FACTOR # PI observer integral gain (bias cancellation) def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.initialized = False self.altitude_error_i = 0 self.acc_scale = 0.0 self.start_time = time.time() self.altitude_error = 0 self.inst_acceleration = 0.0 self.delta = 0 self.estimated_velocity = 0.0 self.estimated_altitude = 0.0 self.last_time = time.time() self.last_orig_altitude = 0 self.last_estimated_altitude = 0 ipcon = IPConnection() self.imu = IMU(UID_IMU, ipcon) self.barometer = Barometer(UID_BAROMETER, ipcon) ipcon.connect(HOST, PORT) # Turn leds and orientation calculation off, to save calculation time # for the IMU Brick. This makes sure that the measurements are taken # in equidistant 2ms intervals self.imu.leds_off() self.imu.orientation_calculation_off() # Turn averaging of in the Barometer Bricklet to make sure that # the data is without delay self.barometer.set_averaging(0, 0, 0) red_pen = QPen(Qt.red) red_pen.setWidth(5) plot_list = [#['', Qt.blue, self.get_orig_value], ['', red_pen, self.get_estimated_value] ] self.plot_widget = PlotWidget('Height [m]', plot_list) self.plot_widget.stop = False self.setCentralWidget(self.plot_widget) self.timer = QTimer() self.timer.timeout.connect(self.update) self.timer.start(6) def get_orig_value(self): return self.last_orig_altitude def get_estimated_value(self): return self.last_estimated_altitude # Update measurements and compute new altitude every 6ms. def update(self): q = self.imu.get_quaternion() acc = self.imu.get_acceleration() alt = self.barometer.get_altitude()/100.0 compensated_acc_q = self.compute_compensated_acc(q, acc) compensated_acc_q_earth = self.compute_dynamic_acceleration_vector(q, compensated_acc_q) self.last_orig_altitude = alt self.last_estimated_altitude = self.compute_altitude(compensated_acc_q_earth[2], alt) # Remove gravity from accelerometer measurements def compute_compensated_acc(self, q, a): g = (2*(q.x*q.z - q.w*q.y), 2*(q.w*q.x + q.y*q.z), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z) return (a[0]/1000.0 - g[0], a[1]/1000.0 - g[1], a[2]/1000.0 - g[2], 0.0) # Rotate dynamic acceleration vector from sensor frame to earth frame def compute_dynamic_acceleration_vector(self, q, compensated_acc_q): def q_conj(q): return -q[0], -q[1], -q[2], q[3] def q_mult(q1, q2): x1, y1, z1, w1 = q1 x2, y2, z2, w2 = q2 w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2 z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 return x, y, z, w tmp = q_mult(q, compensated_acc_q) return q_mult(tmp, q_conj(q)) # Computes estimation of altitude based on barometer and accelerometer measurements # Code is based on blog post from Fabio Varesano: http://www.varesano.net/blog/fabio/complementary-filtering-high-res-barometer-and-accelerometer-reliable-altitude-estimation # He seems to have got the idea from the MultiWii project def compute_altitude(self, compensated_acceleration, altitude): self.current_time = time.time() # Initialization if not self.initialized: self.initialized = True self.estimated_altitude = altitude self.estimated_velocity = 0 self.altitude_error_i = 0 # Estimation Error self.altitude_error = altitude - self.estimated_altitude self.altitude_error_i = self.altitude_error_i + self.altitude_error self.altitude_error_i = min(2500.0, max(-2500.0, self.altitude_error_i)) self.inst_acceleration = compensated_acceleration * 9.80665 + self.altitude_error_i * self.KI dt = self.current_time - self.last_time # Integrators self.delta = self.inst_acceleration * dt + (self.KP1 * dt) * self.altitude_error self.estimated_altitude += (self.estimated_velocity/5.0 + self.delta) * (dt / 2) + (self.KP2 * dt) * self.altitude_error self.estimated_velocity += self.delta*10.0 self.last_time = self.current_time return self.estimated_altitude