-
Notifications
You must be signed in to change notification settings - Fork 203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mpu6050 rewrite #556
Mpu6050 rewrite #556
Changes from all commits
8c80439
44b049c
ae9ac8a
a2124d3
c4c9bdb
d675bd4
6288ba5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,12 +11,25 @@ import ( | |
func main() { | ||
machine.I2C0.Configure(machine.I2CConfig{}) | ||
|
||
accel := mpu6050.New(machine.I2C0) | ||
accel.Configure() | ||
mpuDevice := mpu6050.New(machine.I2C0, mpu6050.DefaultAddress) | ||
|
||
// Configure the device with default configuration. | ||
err := mpuDevice.Configure(mpu6050.Config{}) | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
for { | ||
x, y, z := accel.ReadAcceleration() | ||
println(x, y, z) | ||
time.Sleep(time.Millisecond * 100) | ||
err := mpuDevice.Update() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we should probably solve #345 before merging There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #345 has been merged, so this can be updated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd leave this for last since I still don't have a good rebase methodology. As soon as everything else is reviewed and OK I'll try my hand at rebasing one more time |
||
if err != nil { | ||
println("error reading from mpu6050:", err.Error()) | ||
continue | ||
} | ||
print("acceleration: ") | ||
println(mpuDevice.Acceleration()) | ||
print("angular velocity:") | ||
println(mpuDevice.AngularVelocity()) | ||
print("temperature celsius:") | ||
println(mpuDevice.Temperature() / 1000) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,90 +6,217 @@ | |
// https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf | ||
package mpu6050 // import "tinygo.org/x/drivers/mpu6050" | ||
|
||
import "tinygo.org/x/drivers" | ||
import ( | ||
"encoding/binary" | ||
"errors" | ||
|
||
// Device wraps an I2C connection to a MPU6050 device. | ||
"tinygo.org/x/drivers" | ||
) | ||
|
||
const DefaultAddress = 0x68 | ||
|
||
// RangeAccel defines the range of the accelerometer. | ||
// Allowed values are 2, 4, 8 and 16 with the unit g (gravity). | ||
type RangeAccel uint8 | ||
|
||
// RangeGyro defines the range of the gyroscope. | ||
// Allowed values are 250, 500, 1000 and 2000 with the unit °/s (degree per second). | ||
type RangeGyro uint8 | ||
|
||
var ( | ||
errInvalidRangeAccel = errors.New("mpu6050: invalid range for accelerometer") | ||
errInvalidRangeGyro = errors.New("mpu6050: invalid range for gyroscope") | ||
) | ||
|
||
type Config struct { | ||
// Use ACCEL_RANGE_2 through ACCEL_RANGE_16. | ||
AccelRange RangeAccel | ||
// Use GYRO_RANGE_250 through GYRO_RANGE_2000 | ||
GyroRange RangeGyro | ||
sampleRatio byte // TODO(soypat): expose these as configurable. | ||
clkSel byte | ||
} | ||
|
||
// Device contains MPU board abstraction for usage | ||
type Device struct { | ||
bus drivers.I2C | ||
Address uint16 | ||
conn drivers.I2C | ||
aRange int32 //Gyroscope FSR acording to SetAccelRange input | ||
gRange int32 //Gyroscope FSR acording to SetGyroRange input | ||
Comment on lines
+43
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FSR? |
||
// data contains the accelerometer, gyroscope and temperature data read | ||
// in the last call via the Update method. The data is stored as seven 16bit unsigned | ||
// integers in big endian format: | ||
// | ||
// | ax | ay | az | temp | gx | gy | gz | | ||
data [14]byte | ||
address byte | ||
} | ||
|
||
// New creates a new MPU6050 connection. The I2C bus must already be | ||
// configured. | ||
// | ||
// This function only creates the Device object, it does not touch the device. | ||
func New(bus drivers.I2C) Device { | ||
return Device{bus, Address} | ||
// New instantiates and initializes a MPU6050 struct without writing/reading | ||
// i2c bus. Typical I2C MPU6050 address is 0x68. | ||
func New(bus drivers.I2C, addr uint16) *Device { | ||
p := &Device{} | ||
p.address = uint8(addr) | ||
p.conn = bus | ||
return p | ||
} | ||
|
||
// Init configures the necessary registers for using the | ||
// MPU6050. It sets the range of both the accelerometer | ||
// and the gyroscope, the sample rate, the clock source | ||
// and wakes up the peripheral. | ||
func (p *Device) Configure(data Config) (err error) { | ||
if err = p.Sleep(false); err != nil { | ||
return err | ||
} | ||
if err = p.setClockSource(data.clkSel); err != nil { | ||
return err | ||
} | ||
if err = p.setSampleRate(data.sampleRatio); err != nil { | ||
return err | ||
} | ||
if err = p.setRangeGyro(data.GyroRange); err != nil { | ||
return err | ||
} | ||
if err = p.setRangeAccel(data.AccelRange); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// Connected returns whether a MPU6050 has been found. | ||
// It does a "who am I" request and checks the response. | ||
func (d Device) Connected() bool { | ||
data := []byte{0} | ||
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data) | ||
d.read(_WHO_AM_I, data) | ||
return data[0] == 0x68 | ||
} | ||
|
||
// Configure sets up the device for communication. | ||
func (d Device) Configure() error { | ||
return d.SetClockSource(CLOCK_INTERNAL) | ||
} | ||
|
||
// ReadAcceleration reads the current acceleration from the device and returns | ||
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth | ||
// and the sensor is not moving the returned value will be around 1000000 or | ||
// -1000000. | ||
func (d Device) ReadAcceleration() (x int32, y int32, z int32) { | ||
data := make([]byte, 6) | ||
d.bus.ReadRegister(uint8(d.Address), ACCEL_XOUT_H, data) | ||
// Now do two things: | ||
// 1. merge the two values to a 16-bit number (and cast to a 32-bit integer) | ||
// 2. scale the value to bring it in the -1000000..1000000 range. | ||
// This is done with a trick. What we do here is essentially multiply by | ||
// 1000000 and divide by 16384 to get the original scale, but to avoid | ||
// overflow we do it at 1/64 of the value: | ||
// 1000000 / 64 = 15625 | ||
// 16384 / 64 = 256 | ||
x = int32(int16((uint16(data[0])<<8)|uint16(data[1]))) * 15625 / 256 | ||
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / 256 | ||
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 256 | ||
return | ||
} | ||
|
||
// ReadRotation reads the current rotation from the device and returns it in | ||
// µ°/s (micro-degrees/sec). This means that if you were to do a complete | ||
// Update fetches the latest data from the MPU6050 | ||
func (p *Device) Update() (err error) { | ||
if err = p.read(_ACCEL_XOUT_H, p.data[:]); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// Acceleration returns last read acceleration in µg (micro-gravity). | ||
// When one of the axes is pointing straight to Earth and the sensor is not | ||
// moving the returned value will be around 1000000 or -1000000. | ||
func (d *Device) Acceleration() (ax, ay, az int32) { | ||
const accelOffset = 0 | ||
ax = int32(convertWord(d.data[accelOffset+0:])) * 15625 / 512 * d.aRange | ||
ay = int32(convertWord(d.data[accelOffset+2:])) * 15625 / 512 * d.aRange | ||
az = int32(convertWord(d.data[accelOffset+4:])) * 15625 / 512 * d.aRange | ||
return ax, ay, az | ||
} | ||
|
||
// AngularVelocity reads the current angular velocity from the device and returns it in | ||
// µ°/rad (micro-radians/sec). This means that if you were to do a complete | ||
// rotation along one axis and while doing so integrate all values over time, | ||
// you would get a value close to 360000000. | ||
func (d Device) ReadRotation() (x int32, y int32, z int32) { | ||
data := make([]byte, 6) | ||
d.bus.ReadRegister(uint8(d.Address), GYRO_XOUT_H, data) | ||
// First the value is converted from a pair of bytes to a signed 16-bit | ||
// value and then to a signed 32-bit value to avoid integer overflow. | ||
// Then the value is scaled to µ°/s (micro-degrees per second). | ||
// This is done in the following steps: | ||
// 1. Multiply by 250 * 1000_000 | ||
// 2. Divide by 32768 | ||
// The following calculation (x * 15625 / 2048 * 1000) is essentially the | ||
// same but avoids overflow. First both operations are divided by 16 leading | ||
// to multiply by 15625000 and divide by 2048, and then part of the multiply | ||
// is done after the divide instead of before. | ||
x = int32(int16((uint16(data[0])<<8)|uint16(data[1]))) * 15625 / 2048 * 1000 | ||
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / 2048 * 1000 | ||
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000 | ||
return | ||
} | ||
|
||
// SetClockSource allows the user to configure the clock source. | ||
func (d Device) SetClockSource(source uint8) error { | ||
return d.bus.WriteRegister(uint8(d.Address), PWR_MGMT_1, []uint8{source}) | ||
} | ||
|
||
// SetFullScaleGyroRange allows the user to configure the scale range for the gyroscope. | ||
func (d Device) SetFullScaleGyroRange(rng uint8) error { | ||
return d.bus.WriteRegister(uint8(d.Address), GYRO_CONFIG, []uint8{rng}) | ||
} | ||
|
||
// SetFullScaleAccelRange allows the user to configure the scale range for the accelerometer. | ||
func (d Device) SetFullScaleAccelRange(rng uint8) error { | ||
return d.bus.WriteRegister(uint8(d.Address), ACCEL_CONFIG, []uint8{rng}) | ||
// you would get a value close to 6.3 radians (360 degrees). | ||
func (d *Device) AngularVelocity() (gx, gy, gz int32) { | ||
const angvelOffset = 8 | ||
_ = d.data[angvelOffset+5] // This line fails to compile if RawData is too short. | ||
gx = int32(convertWord(d.data[angvelOffset+0:])) * 4363 / 8192 * d.gRange | ||
gy = int32(convertWord(d.data[angvelOffset+2:])) * 4363 / 8192 * d.gRange | ||
gz = int32(convertWord(d.data[angvelOffset+4:])) * 4363 / 8192 * d.gRange | ||
return gx, gy, gz | ||
} | ||
|
||
// Temperature returns the temperature of the device in milli-centigrade. | ||
func (d *Device) Temperature() (Celsius int32) { | ||
const tempOffset = 6 | ||
return 1506*int32(convertWord(d.data[tempOffset:]))/512 + 37*1000 | ||
} | ||
|
||
func convertWord(buf []byte) int16 { | ||
return int16(binary.BigEndian.Uint16(buf)) | ||
} | ||
|
||
// setSampleRate sets the sample rate for the FIFO, | ||
// register ouput and DMP. The sample rate is determined | ||
// by: | ||
// | ||
// SR = Gyroscope Output Rate / (1 + srDiv) | ||
// | ||
// The Gyroscope Output Rate is 8kHz when the DLPF is | ||
// disabled and 1kHz otherwise. The maximum sample rate | ||
// for the accelerometer is 1kHz, if a higher sample rate | ||
// is chosen, the same accelerometer sample will be output. | ||
func (p *Device) setSampleRate(srDiv byte) (err error) { | ||
// setSampleRate | ||
var sr [1]byte | ||
sr[0] = srDiv | ||
if err = p.write8(_SMPRT_DIV, sr[0]); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// setClockSource configures the source of the clock | ||
// for the peripheral. | ||
func (p *Device) setClockSource(clkSel byte) (err error) { | ||
return p.writeMasked(_PWR_MGMT_1, _CLK_SEL_MASK, clkSel) | ||
} | ||
|
||
// setRangeGyro configures the full scale range of the gyroscope. | ||
// It has four possible values +- 250°/s, 500°/s, 1000°/s, 2000°/s. | ||
func (p *Device) setRangeGyro(gyroRange RangeGyro) (err error) { | ||
switch gyroRange { | ||
case RangeGyro250: | ||
p.gRange = 250 | ||
case RangeGyro500: | ||
p.gRange = 500 | ||
case RangeGyro1000: | ||
p.gRange = 1000 | ||
case RangeGyro2000, rangeGyroDefault: | ||
gyroRange = RangeGyro2000 | ||
p.gRange = 2000 | ||
default: | ||
return errInvalidRangeGyro | ||
} | ||
return p.writeMasked(_GYRO_CONFIG, _G_FS_SEL, uint8(gyroRange-1)<<_G_FS_SHIFT) | ||
} | ||
|
||
// setRangeAccel configures the full scale range of the accelerometer. | ||
// It has four possible values +- 2g, 4g, 8g, 16g. | ||
// The function takes values of accRange from 0-3 where 0 means the | ||
// lowest FSR (2g) and 3 is the highest FSR (16g) | ||
func (p *Device) setRangeAccel(accRange RangeAccel) (err error) { | ||
switch accRange { | ||
case RangeAccel2: | ||
p.aRange = 2 | ||
case RangeAccel4: | ||
p.aRange = 4 | ||
case RangeAccel8: | ||
p.aRange = 8 | ||
case RangeAccel16, rangeAccelDefault: | ||
accRange = RangeAccel16 | ||
p.aRange = 16 | ||
default: | ||
return errInvalidRangeAccel | ||
} | ||
return p.writeMasked(_ACCEL_CONFIG, _AFS_SEL, uint8(accRange-1)<<_AFS_SHIFT) | ||
} | ||
|
||
// Sleep sets the sleep bit on the power managment 1 field. | ||
// When the recieved bool is true, it sets the bit to 1 thus putting | ||
// the peripheral in sleep mode. | ||
// When false is recieved the bit is set to 0 and the peripheral wakes up. | ||
func (p *Device) Sleep(sleepEnabled bool) (err error) { | ||
return p.writeMasked(_PWR_MGMT_1, _SLEEP_MASK, b2u8(sleepEnabled)<<_SLEEP_SHIFT) | ||
} | ||
|
||
func (d *Device) writeMasked(reg byte, mask byte, value byte) error { | ||
var b [1]byte | ||
if err := d.read(reg, b[:]); err != nil { | ||
return err | ||
} | ||
b[0] = (b[0] &^ mask) | value&mask | ||
return d.write8(reg, b[0]) | ||
} | ||
|
||
func b2u8(b bool) byte { | ||
if b { | ||
return 1 | ||
} | ||
return 0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other drivers, this has been solved by exporting
Address
and then just setting it before callingConfigure
. That way we do not have to break the existing API and all of the many projects that use this driver. I have several such projects myself 😺There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With Go modules, I'm not too worried about breakage. Yes, it will break once you do a
go get -u tinygo.org/x/drivers
, but before that it will keep working just fine (and if you're doing ago get -u tinygo.org/x/drivers
, you might as well add the default address).I'm fine either way.