Laelaps  2.3.5
RoadNarrows Robotics Small Outdoor Mobile Robot Project
Imu.py
Go to the documentation of this file.
1 #
2 # Module: Laelaps.Imu
3 #
4 # Package: RoadNarrows Laelaps Robotic Mobile Platform Package
5 #
6 # Link: https://github.com/roadnarrows-robotics/laelaps
7 #
8 # File: Imu.py
9 #
10 ## \file
11 ##
12 ## $LastChangedDate: 2016-02-02 13:47:13 -0700 (Tue, 02 Feb 2016) $
13 ## $Rev: 4293 $
14 ##
15 ## \brief Laelaps Ineria Measurement Unit module.
16 ##
17 ## \author: Robin Knight (robin.knight@roadnarrows.com)
18 ##
19 ## \par Copyright
20 ## \h_copy 2015-2017. RoadNarrows LLC.\n
21 ## http://www.roadnarrows.com\n
22 ## All Rights Reserved
23 #
24 # @EulaBegin@
25 #
26 # Permission is hereby granted, without written agreement and without
27 # license or royalty fees, to use, copy, modify, and distribute this
28 # software and its documentation for any purpose, provided that
29 # (1) The above copyright notice and the following two paragraphs
30 # appear in all copies of the source code and (2) redistributions
31 # including binaries reproduces these notices in the supporting
32 # documentation. Substantial modifications to this software may be
33 # copyrighted by their authors and need not follow the licensing terms
34 # described here, provided that the new terms are clearly indicated in
35 # all files where they apply.
36 #
37 # IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY MEMBERS/EMPLOYEES
38 # OF ROADNARROW LLC OR DISTRIBUTORS OF THIS SOFTWARE BE LIABLE TO ANY
39 # PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
40 # DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
41 # EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN ADVISED OF
42 # THE POSSIBILITY OF SUCH DAMAGE.
43 #
44 # THE AUTHOR AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
45 # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
46 # FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
47 # "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
48 # PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
49 #
50 # @EulaEnd@
51 #
52 
53 import os
54 import serial
55 import struct
56 import time
57 import math
58 
59 
60 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61 # MSP Protocol
62 # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63 
64 #
65 # Message layouts
66 #
67 MSP_PREAMBLE ='$M' # command/response preamble
68 MSP_DIR_TO ='<' # to IMU
69 MSP_DIR_FROM ='>' # from IMU
70 MSP_CMD_PREAMBLE = MSP_PREAMBLE + MSP_DIR_TO # command preamble
71 MSP_RSP_PREAMBLE = MSP_PREAMBLE + MSP_DIR_FROM # response preamble
72 
73 MSP_POS_SIZE = 3 ## data size position in message
74 MSP_POS_CMD_ID = 4 ## command id position in message
75 MSP_POS_DATA = 5 ## start of data position in message
76 
77 ## command header length (preamble + size + cmdid)
78 MSP_CMD_HDR_LEN = len(MSP_CMD_PREAMBLE) + 2
79 
80 ## command minimum length (no data)
81 MSP_CMD_MIN_LEN = MSP_CMD_HDR_LEN + 1
82 
83 ## response header length (preamble + size + cmdid)
84 MSP_RSP_HDR_LEN = len(MSP_RSP_PREAMBLE) + 2
85 
86 ## response minimum length (no data)
87 MSP_RSP_MIN_LEN = MSP_RSP_HDR_LEN + 1
88 
89 #
90 # Command Ids
91 #
92 MSP_IDENT = 100 # get firmware and hardware identities
93 MSP_RAW_IMU = 102 # get raw imu data
94 MSP_ATTITUDE = 108 # get attitude (angx, angy, heading)
95 
96 #
97 # Axes degrees of freedom
98 #
99 X = 0 ## x axis index
100 Y = 1 ## y axis index
101 Z = 2 ## z axis index
102 ROLL = 0 ## roll index
103 PITCH = 1 ## pitch index
104 YAW = 2 ## yaw index
105 NUM_AXES = 3 ## number of axes
106 
107 # Conversion factors and units
108 G = 9.80665 ## standard gravity in m/s^2
109 DEG_TO_RAD = math.pi / 180.0 ## math can save your life
110 
111 MSP_ACCEL_RAW_TO_G = 1.0 / 512.0 ## raw IMU acceleration conversion
112 MSP_GYRO_RAW_TO_DEG_PER_S = 1.0 / 4.096 ## raw IMU rotation conversion
113 MSP_ATTITUDE_RAW_TO_DEG = 0.1 ## attitude conversion
114 
115 UNITS_RAW = 'raw' ## raw sensor values
116 UNITS_G = 'g' ## acceleration in standard g's
117 UNITS_M_PER_S2 = 'm/s^2' ## acceleration in meters/second^2
118 UNITS_DEG_PER_S = 'deg/s' ## degrees/second
119 UNITS_RAD_PER_S = 'rad/s' ## radians/second
120 UNITS_DEG = 'degrees' ## degrees
121 UNITS_RAD = 'radians' ## radians
122 UNITS_SI = 'si' ## international system of units
123  ## (m, s, radians, m/s, m/s^2, radians/s)
124 
125 # ------------------------------------------------------------------------------
126 # ImuException Class
127 # ------------------------------------------------------------------------------
128 
129 ##
130 ## \brief IMU exception class.
131 ##
132 class ImuException(Exception):
133  ##
134  ## \brief Constructor.
135  ##
136  ## \param msg Error message string.
137  ##
138  def __init__(self, msg):
139  ## error message attribute
140  self.message = msg
141 
142  def __repr__(self):
143  return "ImuException(%s)" % (repr(self.message))
144 
145  def __str__(self):
146  return self.message
147 
148 
149 #-------------------------------------------------------------------------------
150 # Imu Class
151 #-------------------------------------------------------------------------------
152 
153 ##
154 ## \brief IMU class.
155 ##
156 class Imu:
157 
158  #
159  ## \brief Constructor.
160  #
161  def __init__(self):
162  self.m_port = None
163 
164  #
165  ## \brief Open connection to IMU.
166  ##
167  ## \param device Serial (symbolic) device name.
168  ## \param baudrate Serial baud rate.
169  #
170  def open(self, device, baudrate):
171  #dev = os.path.join(os.path.dirname(device), os.readlink(device))
172  try:
173  devName = os.readlink(device)
174  dirName = os.path.dirname(device)
175  dev = os.path.join(dirName, devName)
176  except OSError:
177  dev = device
178  try:
179  self.m_port = serial.Serial(dev, baudrate=baudrate, timeout=0.5)
180  except serial.SerialException as inst:
181  raise ImuException(inst.message)
182  self.m_port.flushInput()
183  self.m_port.flushOutput()
184 
185  #
186  ## \brief Close connection with IMU.
187  #
188  def close(self):
189  self.m_port.close()
190 
191  #
192  ## \brief Check if connecton is open.
193  ##
194  ## \return True or False
195  #
196  def isOpen(self):
197  return self.m_port.isOpen()
198 
199  #
200  ## \brief Send command.
201  ##
202  ## \param cmdId Command id.
203  ## \param cmdData Optional command data.
204  #
205  def sendCmd(self, cmdId, cmdData=""):
206  lenData = len(cmdData)
207  cmdBuf = ""
208 
209  for b in MSP_CMD_PREAMBLE:
210  cmdBuf += b
211 
212  cmdBuf += chr(lenData)
213  chksum = lenData
214 
215  cmdBuf += chr(cmdId)
216  chksum ^= cmdId
217 
218  for b in cmdData:
219  cmdBuf += b
220  chksum ^= b
221 
222  chksum &= 0xff
223  cmdBuf += chr(chksum)
224 
225  self.m_port.write(cmdBuf)
226 
227  #
228  ## \brief Receive response.
229  ##
230  ## \param cmdId Command id associated with response.
231  ## \param lenData Optional response data length.
232  #
233  def receiveRsp(self, cmdId, lenData=0):
234  rspLen = MSP_RSP_HDR_LEN + lenData + 1
235  rspBuf = self.m_port.read(rspLen)
236 
237  #print "rspBuf[%d] = '%s'" % (len(rspBuf), rspBuf.encode("hex"))
238 
239  msgLen = len(rspBuf)
240  if msgLen < MSP_RSP_MIN_LEN:
241  raise ImuException("Response too small: " \
242  "Only %d bytes received." % (msgLen))
243 
244  fldSize = ord(rspBuf[MSP_POS_SIZE])
245  if fldSize != lenData:
246  raise ImuException("Response data length mismatch: " \
247  "Received %d bytes, expected %d bytes." % (fldSize, lenData))
248 
249  fldCmdId = ord(rspBuf[MSP_POS_CMD_ID])
250  if fldCmdId != cmdId:
251  raise ImuException("Command Id mismatch: " \
252  "Received %d, expected %d." % (fldCmdId, cmdId))
253 
254  chksum = lenData
255  for b in rspBuf[MSP_POS_CMD_ID:-1]:
256  chksum ^= ord(b)
257  chksum &= 0xff
258 
259  fldChkSum = ord(rspBuf[-1])
260 
261  if chksum != fldChkSum:
262  raise ImuException("Checksum mismatch: " \
263  "Received 0x%02x, calculated 0x%02x." % (fldChkSum, chksum))
264 
265  if lenData > 0:
266  return rspBuf[MSP_POS_DATA:-1]
267  else:
268  return ""
269 
270  #
271  ## \brief Pack an unsigned 8-bit value.
272  ##
273  ## \param val Value to pack.
274  ##
275  ## \return Packed string.
276  #
277  def packU8(self, val):
278  return struct.pack('<B', val)
279 
280  #
281  ## \brief Pack a signed 8-bit value.
282  ##
283  ## \param val Value to pack.
284  ##
285  ## \return Packed string.
286  #
287  def packS8(self, val):
288  return struct.pack('<b', val)
289 
290  #
291  ## \brief Pack an unsigned 16-bit value.
292  ##
293  ## \param val Value to pack.
294  ##
295  ## \return Packed string.
296  #
297  def packU16(self, val):
298  return struct.pack('<H', val)
299 
300  #
301  ## \brief Pack a signed 16-bit value.
302  ##
303  ## \param val Value to pack.
304  ##
305  ## \return Packed string.
306  #
307  def packS16(self, val):
308  return struct.pack('<h', val)
309 
310  #
311  ## \brief Pack an unsigned 32-bit value.
312  ##
313  ## \param val Value to pack.
314  ##
315  ## \return Packed string.
316  #
317  def packU32(self, val):
318  return struct.pack('<I', val)
319 
320  #
321  ## \brief Pack a signed 32-bit value.
322  ##
323  ## \param val Value to pack.
324  ##
325  ## \return Packed string.
326  #
327  def packS32(self, val):
328  return struct.pack('<i', val)
329 
330  #
331  ## \brief Unpack an unsigned 8-bit value from buffer.
332  ##
333  ## \param buf String.
334  ##
335  ## \return Value.
336  #
337  def unpackU8(self, buf):
338  return struct.unpack('<B', buf)[0]
339 
340  #
341  ## \brief Unpack a signed 8-bit value from buffer.
342  ##
343  ## \param buf String.
344  ##
345  ## \return Value.
346  #
347  def unpackS8(self, buf):
348  return struct.unpack('<b', buf)[0]
349 
350  #
351  ## \brief Unpack an unsigned 16-bit value from buffer.
352  ##
353  ## \param buf String.
354  ##
355  ## \return Value.
356  #
357  def unpackU16(self, buf):
358  return struct.unpack('<H', buf)[0]
359 
360  #
361  ## \brief Unpack a signed 16-bit value from buffer.
362  ##
363  ## \param buf String.
364  ##
365  ## \return Value.
366  #
367  def unpackS16(self, buf):
368  return struct.unpack('<h', buf)[0]
369 
370  #
371  ## \brief Unpack an unsigned 32-bit value from buffer.
372  ##
373  ## \param buf String.
374  ##
375  ## \return Value.
376  #
377  def unpackU32(self, buf):
378  return struct.unpack('<I', buf)[0]
379 
380  #
381  ## \brief Unpack a signed 32-bit value from buffer.
382  ##
383  ## \param buf String.
384  ##
385  ## \return Value.
386  #
387  def unpackS32(self, buf):
388  return struct.unpack('<i', buf)[0]
389 
390  def readIdent(self):
391  self.sendCmd(MSP_IDENT)
392 
393  rspData = self.receiveRsp(MSP_IDENT, 7)
394 
395  version = self.unpackU8(rspData[0])
396  multiType = self.unpackU8(rspData[1])
397  mspVersion = self.unpackU8(rspData[2])
398  caps = self.unpackU32(rspData[3:])
399 
400  return {'version':version, 'multi_type':multiType,
401  'msp_version':mspVersion, 'caps':caps}
402 
403  def readRawImu(self, accel_units='raw', gyro_units='raw', mag_units='raw'):
404  accel = NUM_AXES * [0] # raw accelerometer values
405  gyro = NUM_AXES * [0] # raw gyroscope values
406  mag = NUM_AXES * [0] # raw magnetometer values
407 
408  self.sendCmd(MSP_RAW_IMU)
409 
410  rspData = self.receiveRsp(MSP_RAW_IMU, 18)
411 
412  accel[X] = self.unpackS16(rspData[0:2])
413  accel[Y] = self.unpackS16(rspData[2:4])
414  accel[Z] = self.unpackS16(rspData[4:6])
415 
416  gyro[X] = self.unpackS16(rspData[6:8])
417  gyro[Y] = self.unpackS16(rspData[8:10])
418  gyro[Z] = self.unpackS16(rspData[10:12])
419 
420  mag[X] = self.unpackS16(rspData[12:14])
421  mag[Y] = self.unpackS16(rspData[14:16])
422  mag[Z] = self.unpackS16(rspData[16:18])
423 
424  if accel_units == UNITS_G:
425  accel[X] *= MSP_ACCEL_RAW_TO_G
426  accel[Y] *= MSP_ACCEL_RAW_TO_G
427  accel[Z] *= MSP_ACCEL_RAW_TO_G
428  elif accel_units in [UNITS_M_PER_S2, UNITS_SI]:
429  accel[X] = accel[X] * MSP_ACCEL_RAW_TO_G * G
430  accel[Y] = accel[Y] * MSP_ACCEL_RAW_TO_G * G
431  accel[Z] = accel[Z] * MSP_ACCEL_RAW_TO_G * G
432 
433  if gyro_units == UNITS_DEG_PER_S:
434  gyro[X] *= MSP_GYRO_RAW_TO_DEG_PER_S
435  gyro[Y] *= MSP_GYRO_RAW_TO_DEG_PER_S
436  gyro[Z] *= MSP_GYRO_RAW_TO_DEG_PER_S
437  elif gyro_units in [UNITS_RAD_PER_S, UNITS_SI]:
438  gyro[X] = gyro[X] * MSP_GYRO_RAW_TO_DEG_PER_S * DEG_TO_RAD
439  gyro[Y] = gyro[Y] * MSP_GYRO_RAW_TO_DEG_PER_S * DEG_TO_RAD
440  gyro[Z] = gyro[Z] * MSP_GYRO_RAW_TO_DEG_PER_S * DEG_TO_RAD
441 
442  return {'accel':accel, 'gyro':gyro, 'mag':mag}
443 
444  def readAttitude(self, units='raw'):
445  self.sendCmd(MSP_ATTITUDE)
446  #time.sleep(0.1)
447 
448  rspData = self.receiveRsp(MSP_ATTITUDE, 6)
449 
450  roll = self.unpackS16(rspData[0:2])
451  pitch = self.unpackS16(rspData[2:4])
452  yaw = self.unpackS16(rspData[4:6])
453 
454  if units == UNITS_DEG:
455  roll *= MSP_ATTITUDE_RAW_TO_DEG
456  pitch *= MSP_ATTITUDE_RAW_TO_DEG
457  elif units in [UNITS_RAD, UNITS_SI]:
458  roll = roll * MSP_ATTITUDE_RAW_TO_DEG * DEG_TO_RAD
459  pitch = pitch * MSP_ATTITUDE_RAW_TO_DEG * DEG_TO_RAD
460  yaw = yaw * DEG_TO_RAD
461 
462  return (roll, pitch, yaw)
463 
464 
465 # ------------------------------------------------------------------------------
466 # UT main
467 # ------------------------------------------------------------------------------
468 if __name__ == '__main__':
469  print "IMU Unit Test Example"
470  print
471 
472  device = "/dev/ttyUSB0"
473  baud = 115200
474 
475  print "Get raw IMU data:"
476 
477  imu = Imu()
478 
479  imu.open(device, baudrate=baud)
480 
481  ident = imu.readIdent()
482 
483  print "IMU Identity:"
484  print " Version: %u" % (ident['version'])
485  print " Multi-Type: %u" % (ident['multi_type'])
486  print " MSP Version: %u" % (ident['msp_version'])
487  print " Caps: 0x%04x" % (ident['caps'])
488 
489  while True:
490  meas = imu.readRawImu()
491 
492  print "accel_raw(x,y,z) = %6d, %6d, %6d" % \
493  (meas['accel'][X], meas['accel'][Y], meas['accel'][Z])
494  print "gyro_raw(x,y,z) = %6d, %6d, %6d" % \
495  (meas['gyro'][X], meas['gyro'][Y], meas['gyro'][Z])
496 
497  meas = imu.readRawImu('g', 'deg/s')
498 
499  print "accel_g(x,y,z) = %.3f, %.3f, %.3f" % \
500  (meas['accel'][X], meas['accel'][Y], meas['accel'][Z])
501  print "gyro_dps(x,y,z) = %.3f, %.3f, %.3f" % \
502  (meas['gyro'][X], meas['gyro'][Y], meas['gyro'][Z])
503 
504  rpy = imu.readAttitude('degrees')
505  print "roll,pitch,yaw = %.3f, %.3f, %.3f" % \
506  (rpy[ROLL], rpy[PITCH], rpy[YAW])
507 
508  try:
509  time.sleep(1.0)
510  except KeyboardInterrupt:
511  break
def __init__(self)
Constructor.
Definition: Imu.py:161
def packS8(self, val)
Pack a signed 8-bit value.
Definition: Imu.py:287
def unpackS16(self, buf)
Unpack a signed 16-bit value from buffer.
Definition: Imu.py:367
def unpackS32(self, buf)
Unpack a signed 32-bit value from buffer.
Definition: Imu.py:387
def unpackU16(self, buf)
Unpack an unsigned 16-bit value from buffer.
Definition: Imu.py:357
def packU16(self, val)
Pack an unsigned 16-bit value.
Definition: Imu.py:297
def close(self)
Close connection with IMU.
Definition: Imu.py:188
def packS16(self, val)
Pack a signed 16-bit value.
Definition: Imu.py:307
def isOpen(self)
Check if connecton is open.
Definition: Imu.py:196
def unpackU32(self, buf)
Unpack an unsigned 32-bit value from buffer.
Definition: Imu.py:377
def packU8(self, val)
Pack an unsigned 8-bit value.
Definition: Imu.py:277
def unpackU8(self, buf)
Unpack an unsigned 8-bit value from buffer.
Definition: Imu.py:337
def packS32(self, val)
Pack a signed 32-bit value.
Definition: Imu.py:327
def sendCmd(self, cmdId, cmdData="")
Send command.
Definition: Imu.py:205
def packU32(self, val)
Pack an unsigned 32-bit value.
Definition: Imu.py:317
IMU class.
Definition: Imu.py:156
message
error message attribute
Definition: Imu.py:140
def receiveRsp(self, cmdId, lenData=0)
Receive response.
Definition: Imu.py:233
def unpackS8(self, buf)
Unpack a signed 8-bit value from buffer.
Definition: Imu.py:347
international system of units (m, s, radians, m/s, m/s^2, radians/s)
Definition: Imu.py:132
def open(self, device, baudrate)
Open connection to IMU.
Definition: Imu.py:170
def __init__(self, msg)
Constructor.
Definition: Imu.py:138