gobot.io/x/gobot/v2@v2.1.0/drivers/i2c/mpu6050_driver.go (about)

     1  package i2c
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"log"
     8  	"time"
     9  )
    10  
    11  const (
    12  	mpu6050Debug                = false
    13  	mpu6050DefaultAddress       = 0x68
    14  	mpu6050EarthStandardGravity = 9.80665 // [m/s²] standard gravity (pole: 9.834,  equator: 9.764)
    15  )
    16  
    17  type MPU6050DlpfConfig uint8
    18  type MPU6050FrameSyncConfig uint8
    19  type MPU6050GyroFsConfig uint8
    20  type MPU6050AccelFsConfig uint8
    21  type MPU6050Pwr1ClockConfig uint8
    22  
    23  const (
    24  	mpu6050Reg_GeneralConfig   = 0x1A // external frame synchronization and digital low pass filter
    25  	mpu6050Reg_GyroConfig      = 0x1B // self test and full scale range
    26  	mpu6050Reg_AccelConfig     = 0x1C // self test and full scale range
    27  	mpu6050Reg_AccelXoutH      = 0x3B // first data register
    28  	mpu6050Reg_SignalPathReset = 0x68
    29  	mpu6050Reg_PwrMgmt1        = 0x6B
    30  
    31  	MPU6050General_Dlpf260Hz MPU6050DlpfConfig = 0x00
    32  	MPU6050General_Dlpf184Hz MPU6050DlpfConfig = 0x01
    33  	MPU6050General_Dlpf94Hz  MPU6050DlpfConfig = 0x02
    34  	MPU6050General_Dlpf44Hz  MPU6050DlpfConfig = 0x03
    35  	MPU6050General_Dlpf21Hz  MPU6050DlpfConfig = 0x04
    36  	MPU6050General_Dlpf10Hz  MPU6050DlpfConfig = 0x05
    37  	MPU6050General_Dlpf5Hz   MPU6050DlpfConfig = 0x06
    38  
    39  	MPU6050General_FrameSyncDisabled MPU6050FrameSyncConfig = 0x00
    40  	MPU6050General_FrameSyncTemp     MPU6050FrameSyncConfig = 0x01
    41  	MPU6050General_FrameSyncGyroX    MPU6050FrameSyncConfig = 0x02
    42  	MPU6050General_FrameSyncGyroY    MPU6050FrameSyncConfig = 0x03
    43  	MPU6050General_FrameSyncGyroZ    MPU6050FrameSyncConfig = 0x04
    44  	MPU6050General_FrameSyncAccelX   MPU6050FrameSyncConfig = 0x05
    45  	MPU6050General_FrameSyncAccelY   MPU6050FrameSyncConfig = 0x06
    46  	MPU6050General_FrameSyncAccelZ   MPU6050FrameSyncConfig = 0x07
    47  
    48  	MPU6050Gyro_FsSel250dps  MPU6050GyroFsConfig = 0x00 // +/- 250 °/s
    49  	MPU6050Gyro_FsSel500dps  MPU6050GyroFsConfig = 0x01 // +/- 500 °/s
    50  	MPU6050Gyro_FsSel1000dps MPU6050GyroFsConfig = 0x02 // +/- 1000 °/s
    51  	MPU6050Gyro_FsSel2000dps MPU6050GyroFsConfig = 0x03 // +/- 2000 °/s
    52  
    53  	MPU6050Accel_AFsSel2g  MPU6050AccelFsConfig = 0x00 // +/- 2 g
    54  	MPU6050Accel_AFsSel4g  MPU6050AccelFsConfig = 0x01 // +/- 4 g
    55  	MPU6050Accel_AFsSel8g  MPU6050AccelFsConfig = 0x02 // +/- 8 g
    56  	MPU6050Accel_AFsSel16g MPU6050AccelFsConfig = 0x03 // +/- 16 g
    57  
    58  	mpu6050SignalReset_TempBit  = 0x01
    59  	mpu6050SignalReset_AccelBit = 0x02
    60  	mpu6050SignalReset_GyroBit  = 0x04
    61  
    62  	MPU6050Pwr1_ClockIntern8G  MPU6050Pwr1ClockConfig = 0x00 // internal 8GHz
    63  	MPU6050Pwr1_ClockPllXGyro  MPU6050Pwr1ClockConfig = 0x01 // PLL with X axis gyroscope reference
    64  	MPU6050Pwr1_ClockPllYGyro  MPU6050Pwr1ClockConfig = 0x02 // PLL with Y axis gyroscope reference
    65  	MPU6050Pwr1_ClockPllZGyro  MPU6050Pwr1ClockConfig = 0x03 // PLL with Z axis gyroscope reference
    66  	MPU6050Pwr1_ClockPllExt32K MPU6050Pwr1ClockConfig = 0x04 // PLL with external 32.768kHz reference
    67  	MPU6050Pwr1_ClockPllExt19M MPU6050Pwr1ClockConfig = 0x05 // PLL with external 19.2MHz reference
    68  	MPU6050Pwr1_ClockStop      MPU6050Pwr1ClockConfig = 0x07 // Stops the clock and keeps the timing generator in reset
    69  
    70  	mpu6050Pwr1_SleepOnBit     = 0x40 // put into low power sleep mode
    71  	mpu6050Pwr1_DeviceResetBit = 0x80
    72  )
    73  
    74  type MPU6050ThreeDData struct {
    75  	X float64
    76  	Y float64
    77  	Z float64
    78  }
    79  
    80  // MPU6050Driver is a Gobot Driver for an MPU6050 I2C Accelerometer/Gyroscope/Temperature sensor.
    81  //
    82  // This driver was tested with Tinkerboard & Digispark adaptor and a MPU6050 breakout board GY-521,
    83  // available from various distributors.
    84  //
    85  // datasheet:
    86  // https://product.tdk.com/system/files/dam/doc/product/sensor/mortion-inertial/imu/data_sheet/mpu-6000-datasheet1.pdf
    87  //
    88  // reference implementations:
    89  // * https://github.com/adafruit/Adafruit_CircuitPython_MPU6050
    90  // * https://github.com/ElectronicCats/mpu6050
    91  type MPU6050Driver struct {
    92  	*Driver
    93  	Accelerometer MPU6050ThreeDData
    94  	Gyroscope     MPU6050ThreeDData
    95  	Temperature   float64
    96  	dlpf          MPU6050DlpfConfig
    97  	frameSync     MPU6050FrameSyncConfig
    98  	accelFs       MPU6050AccelFsConfig
    99  	gyroFs        MPU6050GyroFsConfig
   100  	clock         MPU6050Pwr1ClockConfig
   101  	gravity       float64 // set to 1.0 leads to [g]
   102  }
   103  
   104  // mpu6050AccelGain in 1/g
   105  var mpu6050AccelGain = map[MPU6050AccelFsConfig]uint16{
   106  	MPU6050Accel_AFsSel2g:  16384,
   107  	MPU6050Accel_AFsSel4g:  8192,
   108  	MPU6050Accel_AFsSel8g:  4096,
   109  	MPU6050Accel_AFsSel16g: 2028,
   110  }
   111  
   112  // mpu6050GyroGain in s/°
   113  var mpu6050GyroGain = map[MPU6050GyroFsConfig]float64{
   114  	MPU6050Gyro_FsSel250dps:  131.0,
   115  	MPU6050Gyro_FsSel500dps:  65.5,
   116  	MPU6050Gyro_FsSel1000dps: 32.8,
   117  	MPU6050Gyro_FsSel2000dps: 16.4,
   118  }
   119  
   120  // NewMPU6050Driver creates a new Gobot Driver for an MPU6050 I2C Accelerometer/Gyroscope/Temperature sensor.
   121  //
   122  // Params:
   123  //		conn Connector - the Adaptor to use with this Driver
   124  //
   125  // Optional params:
   126  //		i2c.WithBus(int):	bus to use with this driver
   127  //		i2c.WithAddress(int):	address to use with this driver
   128  //
   129  func NewMPU6050Driver(a Connector, options ...func(Config)) *MPU6050Driver {
   130  	m := &MPU6050Driver{
   131  		Driver:    NewDriver(a, "MPU6050", mpu6050DefaultAddress),
   132  		dlpf:      MPU6050General_Dlpf260Hz,
   133  		frameSync: MPU6050General_FrameSyncDisabled,
   134  		accelFs:   MPU6050Accel_AFsSel2g,
   135  		gyroFs:    MPU6050Gyro_FsSel250dps,
   136  		clock:     MPU6050Pwr1_ClockPllXGyro,
   137  		gravity:   mpu6050EarthStandardGravity,
   138  	}
   139  	m.afterStart = m.initialize
   140  
   141  	for _, option := range options {
   142  		option(m)
   143  	}
   144  
   145  	// TODO: add commands to API
   146  	return m
   147  }
   148  
   149  // WithMPU6050DigitalFilter option sets the digital low pass filter bandwidth frequency.
   150  // Valid settings are of type "MPU6050DlpfConfig"
   151  func WithMPU6050DigitalFilter(val MPU6050DlpfConfig) func(Config) {
   152  	return func(c Config) {
   153  		if d, ok := c.(*MPU6050Driver); ok {
   154  			d.dlpf = val
   155  		} else if mpu6050Debug {
   156  			log.Printf("Trying to set digital low pass filter for non-MPU6050Driver %v", c)
   157  		}
   158  	}
   159  }
   160  
   161  // WithMPU6050FrameSync option sets the external frame synchronization.
   162  // Valid settings are of type "MPU6050FrameSyncConfig"
   163  func WithMPU6050FrameSync(val MPU6050FrameSyncConfig) func(Config) {
   164  	return func(c Config) {
   165  		if d, ok := c.(*MPU6050Driver); ok {
   166  			d.frameSync = val
   167  		} else if mpu6050Debug {
   168  			log.Printf("Trying to set external frame synchronization for non-MPU6050Driver %v", c)
   169  		}
   170  	}
   171  }
   172  
   173  // WithMPU6050AccelFullScaleRange option sets the full scale range for the accelerometer.
   174  // Valid settings are of type "MPU6050AccelFsConfig"
   175  func WithMPU6050AccelFullScaleRange(val MPU6050AccelFsConfig) func(Config) {
   176  	return func(c Config) {
   177  		if d, ok := c.(*MPU6050Driver); ok {
   178  			d.accelFs = val
   179  		} else if mpu6050Debug {
   180  			log.Printf("Trying to set full scale range of accelerometer for non-MPU6050Driver %v", c)
   181  		}
   182  	}
   183  }
   184  
   185  // WithMPU6050GyroFullScaleRange option sets the full scale range for the gyroscope.
   186  // Valid settings are of type "MPU6050GyroFsConfig"
   187  func WithMPU6050GyroFullScaleRange(val MPU6050GyroFsConfig) func(Config) {
   188  	return func(c Config) {
   189  		if d, ok := c.(*MPU6050Driver); ok {
   190  			d.gyroFs = val
   191  		} else if mpu6050Debug {
   192  			log.Printf("Trying to set full scale range of gyroscope for non-MPU6050Driver %v", c)
   193  		}
   194  	}
   195  }
   196  
   197  // WithMPU6050ClockSource option sets the clock source.
   198  // Valid settings are of type "MPU6050Pwr1ClockConfig"
   199  func WithMPU6050ClockSource(val MPU6050Pwr1ClockConfig) func(Config) {
   200  	return func(c Config) {
   201  		if d, ok := c.(*MPU6050Driver); ok {
   202  			d.clock = val
   203  		} else if mpu6050Debug {
   204  			log.Printf("Trying to set clock source for non-MPU6050Driver %v", c)
   205  		}
   206  	}
   207  }
   208  
   209  // WithMPU6050Gravity option sets the gravity in [m/s²/g].
   210  // Useful settings are "1.0" (to use unit "g") or a value between 9.834 (pole) and 9.764 (equator)
   211  func WithMPU6050Gravity(val float64) func(Config) {
   212  	return func(c Config) {
   213  		if d, ok := c.(*MPU6050Driver); ok {
   214  			d.gravity = val
   215  		} else if mpu6050Debug {
   216  			log.Printf("Trying to set gravity for non-MPU6050Driver %v", c)
   217  		}
   218  	}
   219  }
   220  
   221  // GetData fetches the latest data from the MPU6050
   222  func (m *MPU6050Driver) GetData() (err error) {
   223  	m.mutex.Lock()
   224  	defer m.mutex.Unlock()
   225  
   226  	data := make([]byte, 14)
   227  	err = m.connection.ReadBlockData(mpu6050Reg_AccelXoutH, data)
   228  	if err != nil {
   229  		return
   230  	}
   231  
   232  	var accel struct {
   233  		X int16
   234  		Y int16
   235  		Z int16
   236  	}
   237  	var temp int16
   238  	var gyro struct {
   239  		X int16
   240  		Y int16
   241  		Z int16
   242  	}
   243  
   244  	buf := bytes.NewBuffer(data)
   245  	binary.Read(buf, binary.BigEndian, &accel)
   246  	binary.Read(buf, binary.BigEndian, &temp)
   247  	binary.Read(buf, binary.BigEndian, &gyro)
   248  
   249  	ag := float64(mpu6050AccelGain[m.accelFs]) / m.gravity
   250  	m.Accelerometer.X = float64(accel.X) / ag
   251  	m.Accelerometer.Y = float64(accel.Y) / ag
   252  	m.Accelerometer.Z = float64(accel.Z) / ag
   253  
   254  	m.Temperature = float64(temp)/340 + 36.53
   255  
   256  	gg := mpu6050GyroGain[m.gyroFs]
   257  	m.Gyroscope.X = float64(gyro.X) / gg
   258  	m.Gyroscope.Y = float64(gyro.Y) / gg
   259  	m.Gyroscope.Z = float64(gyro.Z) / gg
   260  
   261  	return
   262  }
   263  
   264  func (m *MPU6050Driver) waitForReset() error {
   265  	wait := 100 * time.Millisecond
   266  	start := time.Now()
   267  	for {
   268  		if time.Since(start) > wait {
   269  			return fmt.Errorf("timeout on wait for reset is done")
   270  		}
   271  		if val, err := m.connection.ReadByteData(mpu6050Reg_PwrMgmt1); (val&mpu6050Pwr1_DeviceResetBit == 0) && (err == nil) {
   272  			return nil
   273  		}
   274  		time.Sleep(wait / 10)
   275  	}
   276  }
   277  
   278  func (m *MPU6050Driver) initialize() (err error) {
   279  	// reset device and wait for reset is finished
   280  	if err = m.connection.WriteByteData(mpu6050Reg_PwrMgmt1, mpu6050Pwr1_DeviceResetBit); err != nil {
   281  		return
   282  	}
   283  	if err = m.waitForReset(); err != nil {
   284  		return
   285  	}
   286  
   287  	// reset signal path register
   288  	reset := uint8(mpu6050SignalReset_TempBit | mpu6050SignalReset_AccelBit | mpu6050SignalReset_GyroBit)
   289  	if err = m.connection.WriteByteData(mpu6050Reg_SignalPathReset, reset); err != nil {
   290  		return
   291  	}
   292  	time.Sleep(100 * time.Millisecond)
   293  
   294  	// configure digital filter bandwidth and external frame synchronization (bits 3...5 are used)
   295  	generalConf := uint8(m.dlpf) | uint8(m.frameSync)<<3
   296  	if err = m.connection.WriteByteData(mpu6050Reg_GeneralConfig, generalConf); err != nil {
   297  		return
   298  	}
   299  
   300  	// set full scale range of gyroscope (bits 3 and 4 are used)
   301  	if err = m.connection.WriteByteData(mpu6050Reg_GyroConfig, uint8(m.gyroFs)<<3); err != nil {
   302  		return
   303  	}
   304  
   305  	// set full scale range of accelerometer (bits 3 and 4 are used)
   306  	if err = m.connection.WriteByteData(mpu6050Reg_AccelConfig, uint8(m.accelFs)<<3); err != nil {
   307  		return
   308  	}
   309  
   310  	// set clock source and reset sleep
   311  	pwr1 := uint8(m.clock) & ^uint8(mpu6050Pwr1_SleepOnBit)
   312  	if err = m.connection.WriteByteData(mpu6050Reg_PwrMgmt1, pwr1); err != nil {
   313  		return
   314  	}
   315  
   316  	return
   317  }