Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added bmi270/bmi270-config.bin
Binary file not shown.
272 changes: 272 additions & 0 deletions bmi270/bmi270.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
// Package bmi270 provides a driver for the BMI270 6-axis inertial measurement unit.
//
// Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi270-ds000.pdf
package bmi270

import (
_ "embed"
"errors"
"time"

"tinygo.org/x/drivers"
)

//go:embed bmi270-config.bin
var bmi270ConfigData string

const Address = 0x68

type AccelRange uint8

const (
Accel2G AccelRange = 0x00
Accel4G AccelRange = 0x01
Accel8G AccelRange = 0x02
Accel16G AccelRange = 0x03
)

type GyroRange uint8

const (
Gyro2000DPS GyroRange = 0x00
Gyro1000DPS GyroRange = 0x01
Gyro500DPS GyroRange = 0x02
Gyro250DPS GyroRange = 0x03
Gyro125DPS GyroRange = 0x04
)

type Device struct {
bus drivers.I2C
address uint8
accelRange AccelRange
gyroRange GyroRange
wbuf [17]byte
rbuf [6]byte
}

type Config struct {
AccelRange AccelRange
GyroRange GyroRange
}

func DefaultConfig() Config {
return Config{
AccelRange: Accel2G,
GyroRange: Gyro2000DPS,
}
}

var (
errNotConnected = errors.New("bmi270: not connected")
errInitFailed = errors.New("bmi270: initialization failed")
)

func NewI2C(bus drivers.I2C, address uint8) *Device {
return &Device{
bus: bus,
address: address,
}
}

func (d *Device) Connected() bool {
val, err := d.read1(reg_CHIP_ID)
return err == nil && val == chipIDBMI270
}

func (d *Device) Configure(config Config) error {
if config.AccelRange != 0 {
d.accelRange = config.AccelRange
} else {
d.accelRange = Accel2G
}

if config.GyroRange != 0 {
d.gyroRange = config.GyroRange
} else {
d.gyroRange = Gyro2000DPS
}

if !d.Connected() {
return errNotConnected
}

if err := d.write1(reg_CMD, 0xB6); err != nil {
return err
}
time.Sleep(200 * time.Millisecond)

if err := d.write1(reg_PWR_CONF, 0x00); err != nil {
return err
}
time.Sleep(1 * time.Millisecond)

if err := d.write1(reg_INIT_CTRL, 0x00); err != nil {
return err
}
time.Sleep(1 * time.Millisecond)

configBytes := []byte(bmi270ConfigData)
chunkSize := 16
for i := 0; i < len(configBytes); i += chunkSize {
end := i + chunkSize
if end > len(configBytes) {
end = len(configBytes)
}

wordAddr := uint16(i / 2)
addrLow := byte(wordAddr & 0x0F)
addrHigh := byte(wordAddr >> 4)
d.wbuf[0] = reg_INIT_ADDR_0
d.wbuf[1] = addrLow
d.wbuf[2] = addrHigh
if err := d.bus.Tx(uint16(d.address), d.wbuf[:3], nil); err != nil {
return err
}

chunk := configBytes[i:end]
d.wbuf[0] = reg_INIT_DATA
copy(d.wbuf[1:], chunk)
if err := d.bus.Tx(uint16(d.address), d.wbuf[:1+len(chunk)], nil); err != nil {
return err
}
}

if err := d.write1(reg_INIT_CTRL, 0x01); err != nil {
return err
}
time.Sleep(200 * time.Millisecond)

start := time.Now()
for {
status, err := d.read1(reg_INTERNAL_STATUS)
if err != nil {
return err
}
if status == 0x01 {
break
}
if time.Since(start) >= 500*time.Millisecond {
return errInitFailed
}
time.Sleep(50 * time.Millisecond)
}

if err := d.write1(reg_ACC_CONF, 0xA8); err != nil {
return err
}

var rangeVal byte
switch d.accelRange {
case Accel2G:
rangeVal = 0x00
case Accel4G:
rangeVal = 0x01
case Accel8G:
rangeVal = 0x02
case Accel16G:
rangeVal = 0x03
default:
rangeVal = 0x00
}
if err := d.write1(reg_ACC_RANGE, rangeVal); err != nil {
return err
}

if err := d.write1(reg_GYR_CONF, 0xA8); err != nil {
return err
}

var gyroRangeVal byte
switch d.gyroRange {
case Gyro2000DPS:
gyroRangeVal = 0x00
case Gyro1000DPS:
gyroRangeVal = 0x01
case Gyro500DPS:
gyroRangeVal = 0x02
case Gyro250DPS:
gyroRangeVal = 0x03
case Gyro125DPS:
gyroRangeVal = 0x04
default:
gyroRangeVal = 0x00
}
if err := d.write1(reg_GYR_RANGE, gyroRangeVal); err != nil {
return err
}

if err := d.write1(reg_PWR_CTRL, 0x06); err != nil {
return err
}
time.Sleep(50 * time.Millisecond)

return nil
}

// ReadAcceleration returns the acceleration in µg (micro-gravity).
// When one of the axes is pointing straight down and the sensor
// is not moving, the returned value will be around 1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
if err = d.bus.Tx(uint16(d.address), []byte{reg_ACC_DATA}, d.rbuf[:6]); err != nil {
return 0, 0, 0, err
}

rawX := int16(uint16(d.rbuf[1])<<8 | uint16(d.rbuf[0]))
rawY := int16(uint16(d.rbuf[3])<<8 | uint16(d.rbuf[2]))
rawZ := int16(uint16(d.rbuf[5])<<8 | uint16(d.rbuf[4]))

k := int32(61)
switch d.accelRange {
case Accel4G:
k = 122
case Accel8G:
k = 244
case Accel16G:
k = 488
}

x = int32(rawX) * k
y = int32(rawY) * k
z = int32(rawZ) * k
return
}

// ReadRotation returns the angular velocity in µdps (micro-degrees/second).
func (d *Device) ReadRotation() (x, y, z int32, err error) {
if err = d.bus.Tx(uint16(d.address), []byte{reg_GYR_DATA}, d.rbuf[:6]); err != nil {
return 0, 0, 0, err
}

rawX := int16(uint16(d.rbuf[1])<<8 | uint16(d.rbuf[0]))
rawY := int16(uint16(d.rbuf[3])<<8 | uint16(d.rbuf[2]))
rawZ := int16(uint16(d.rbuf[5])<<8 | uint16(d.rbuf[4]))

k := int32(60976)
switch d.gyroRange {
case Gyro1000DPS:
k = 30488
case Gyro500DPS:
k = 15244
case Gyro250DPS:
k = 7622
case Gyro125DPS:
k = 3811
}

x = int32(rawX) * k
y = int32(rawY) * k
z = int32(rawZ) * k
return
}

func (d *Device) read1(register uint8) (uint8, error) {
d.wbuf[0] = register
err := d.bus.Tx(uint16(d.address), d.wbuf[:1], d.rbuf[:1])
return d.rbuf[0], err
}

func (d *Device) write1(register, val uint8) error {
d.wbuf[0] = register
d.wbuf[1] = val
return d.bus.Tx(uint16(d.address), d.wbuf[:2], nil)
}
21 changes: 21 additions & 0 deletions bmi270/registers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package bmi270

const (
reg_CHIP_ID = 0x00
reg_ACC_DATA = 0x0C
reg_GYR_DATA = 0x12
reg_INTERNAL_STATUS = 0x21
reg_ACC_CONF = 0x40
reg_ACC_RANGE = 0x41
reg_GYR_CONF = 0x42
reg_GYR_RANGE = 0x43
reg_INIT_CTRL = 0x59
reg_INIT_ADDR_0 = 0x5B
reg_INIT_ADDR_1 = 0x5C
reg_INIT_DATA = 0x5E
reg_PWR_CONF = 0x7C
reg_PWR_CTRL = 0x7D
reg_CMD = 0x7E

chipIDBMI270 = 0x24
)
48 changes: 48 additions & 0 deletions examples/bmi270/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"fmt"
"machine"
"time"

"tinygo.org/x/drivers/bmi270"
)

func main() {
time.Sleep(5 * time.Second)

machine.I2C0.Configure(machine.I2CConfig{
SCL: machine.SCL0_PIN,
SDA: machine.SDA0_PIN,
})

sensor := bmi270.NewI2C(machine.I2C0, bmi270.Address)
if !sensor.Connected() {
println("BMI270 not connected")
return
}

cfg := bmi270.DefaultConfig()
if err := sensor.Configure(cfg); err != nil {
println("BMI270 configuration failed:", err.Error())
return
}

for {
time.Sleep(time.Second)

accelX, accelY, accelZ, err := sensor.ReadAcceleration()
if err != nil {
println("Error reading acceleration:", err.Error())
continue
}
fmt.Printf("Acceleration: %.2fg %.2fg %.2fg\n", float32(accelX)/1e6, float32(accelY)/1e6, float32(accelZ)/1e6)

gyroX, gyroY, gyroZ, err := sensor.ReadRotation()
if err != nil {
println("Error reading rotation:", err.Error())
continue
}
fmt.Printf("Rotation: %.2f°/s %.2f°/s %.2f°/s\n", float32(gyroX)/1e6, float32(gyroY)/1e6, float32(gyroZ)/1e6)
}
}
1 change: 1 addition & 0 deletions smoketest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bh1
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/blinkm/main.go
tinygo build -size short -o ./build/test.hex -target=pinetime ./examples/bma42x/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmi160/main.go
tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/bmi270/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go
Expand Down