mirror of https://github.com/periph/devices
Implement TLV493D hall effect sensor on I2C bus (#450)
parent
99245bb350
commit
8a5f0035b2
@ -0,0 +1,23 @@
|
||||
// Copyright 2020 The Periph Authors. All rights reserved.
|
||||
// Use of this source code is governed under the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
// Package tlv493d implements interfacing code to the Infineon TLV493D haff effect sensor.
|
||||
//
|
||||
// Features of the device:
|
||||
// 3-dimensional hall effect sensor, measures up to +/-130 mT magnetic flux.
|
||||
// temperature sensor
|
||||
// i2c interface
|
||||
// 12-bit resolution
|
||||
// low power consumption
|
||||
//
|
||||
// Features of the driver:
|
||||
// Implemented all options of the device
|
||||
// Power modes described in the documentation are defined as constants
|
||||
// 2 precisions: high precision (12 bits), where all registers are read or low precision, which saves 50% of I2C bandwidth, but without temperature and only 8-bit resolution
|
||||
// Continuous reading mode
|
||||
//
|
||||
// Datasheet and application notes:
|
||||
// https://www.infineon.com/cms/en/product/sensor/magnetic-sensors/magnetic-position-sensors/3d-magnetics/tlv493d-a1b6/
|
||||
//
|
||||
package tlv493d
|
||||
@ -0,0 +1,58 @@
|
||||
// Copyright 2020 The Periph Authors. All rights reserved.
|
||||
// Use of this source code is governed under the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package tlv493d_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"periph.io/x/periph/conn/i2c/i2creg"
|
||||
"periph.io/x/periph/conn/physic"
|
||||
"periph.io/x/periph/experimental/devices/tlv493d"
|
||||
"periph.io/x/periph/host"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// Make sure periph is initialized.
|
||||
if _, err := host.Init(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Open default I²C bus.
|
||||
bus, err := i2creg.Open("")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open I²C: %v", err)
|
||||
}
|
||||
defer bus.Close()
|
||||
|
||||
// Create a new TLV493D hall effect sensor.
|
||||
tlv, err := tlv493d.New(bus, &tlv493d.DefaultOpts)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer tlv.Halt()
|
||||
|
||||
// Read a single value.
|
||||
tlv.SetMode(tlv493d.LowPowerMode)
|
||||
fmt.Println("Single reading")
|
||||
reading, err := tlv.Read(tlv493d.HighPrecisionWithTemperature)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
fmt.Println(reading)
|
||||
|
||||
// Read values continuously from the sensor.
|
||||
fmt.Println("Continuous reading")
|
||||
c, err := tlv.ReadContinuous(100*physic.Hertz, tlv493d.LowPrecision)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for reading := range c {
|
||||
fmt.Println(reading)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,473 @@
|
||||
// Copyright 2020 The Periph Authors. All rights reserved.
|
||||
// Use of this source code is governed under the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package tlv493d
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"periph.io/x/periph/conn/i2c"
|
||||
"periph.io/x/periph/conn/physic"
|
||||
)
|
||||
|
||||
// I2CAddr is the default I2C address for the TLV493D component.
|
||||
const I2CAddr uint16 = 0x5e
|
||||
|
||||
// I2CAddr1 is an alternative I2C address for TLV493D components.
|
||||
const I2CAddr1 uint16 = 0x1f
|
||||
|
||||
// Precision represents a request for a compromise between I2C bandwidth
|
||||
// versus measurement precision.
|
||||
type Precision int
|
||||
|
||||
const (
|
||||
// HighPrecisionWithTemperature reads the full 12-bit value for each axis
|
||||
// and the temperature
|
||||
HighPrecisionWithTemperature Precision = 0
|
||||
// LowPrecision reads only 8-bits for each axis. Temperature is not read.
|
||||
LowPrecision Precision = 1
|
||||
)
|
||||
|
||||
const (
|
||||
numberOfReadRegisters = 10
|
||||
numberOfMeasurementRegisters = 7
|
||||
numberOfFastMeasurementRegisters = 3
|
||||
startupDelay = 40 * time.Millisecond
|
||||
commandRecovery = 0xff
|
||||
|
||||
registerBx = 0
|
||||
registerBy = 1
|
||||
registerBz = 2
|
||||
registerTemp = 3
|
||||
registerBx2 = 4
|
||||
registerBz2 = 5
|
||||
registerTemp2 = 6
|
||||
registerFactSet1 = 7
|
||||
registerFactSet2 = 8
|
||||
registerFactSet3 = 9
|
||||
|
||||
bitParity = 7
|
||||
bitFastMode = 1
|
||||
bitLowPowerMode = 0
|
||||
bitLowPowerPeriod = 6
|
||||
bitInterruptPad = 2
|
||||
bitTemperatureMeasurement = 7
|
||||
bitParityTest = 5
|
||||
|
||||
magneticFluxScaling = 98 * physic.MicroTesla
|
||||
temperatureScaling = 1100 * physic.MilliCelsius
|
||||
temperatureScalingDiv = 10
|
||||
referenceTemperature = 25*physic.Celsius + physic.ZeroCelsius
|
||||
)
|
||||
|
||||
// Mode reprents the various power modes described in the documentation
|
||||
type Mode struct {
|
||||
fastMode bool
|
||||
lowPowerMode bool
|
||||
lowPowerPeriod bool
|
||||
timeToMeasure time.Duration
|
||||
measurementFrequency physic.Frequency
|
||||
}
|
||||
|
||||
// PowerDownMode shuts down the sensor. It can still reply to I2C commands.
|
||||
var PowerDownMode = Mode{
|
||||
fastMode: false,
|
||||
lowPowerMode: false,
|
||||
lowPowerPeriod: false,
|
||||
timeToMeasure: 10 * time.Millisecond,
|
||||
measurementFrequency: 1 * physic.Hertz,
|
||||
}
|
||||
|
||||
// FastMode is the mode using the most energy
|
||||
var FastMode = Mode{
|
||||
fastMode: true,
|
||||
lowPowerMode: false,
|
||||
lowPowerPeriod: false,
|
||||
timeToMeasure: 0,
|
||||
measurementFrequency: 3300 * physic.Hertz,
|
||||
}
|
||||
|
||||
// LowPowerMode uses less energy than FastMode, with lower measurement rate
|
||||
var LowPowerMode = Mode{
|
||||
fastMode: false,
|
||||
lowPowerMode: true,
|
||||
lowPowerPeriod: true,
|
||||
timeToMeasure: 0,
|
||||
measurementFrequency: 100 * physic.Hertz,
|
||||
}
|
||||
|
||||
// UltraLowPowerMode saves the most energy but with very low measurement rate
|
||||
var UltraLowPowerMode = Mode{
|
||||
fastMode: false,
|
||||
lowPowerMode: true,
|
||||
lowPowerPeriod: false,
|
||||
timeToMeasure: 0,
|
||||
measurementFrequency: 10 * physic.Hertz,
|
||||
}
|
||||
|
||||
// MasterControlledMode refer to TLV493D documentation on how to use this mode
|
||||
var MasterControlledMode = Mode{
|
||||
fastMode: true,
|
||||
lowPowerMode: true,
|
||||
lowPowerPeriod: true,
|
||||
timeToMeasure: 0,
|
||||
measurementFrequency: 3300 * physic.Hertz,
|
||||
}
|
||||
|
||||
// Opts holds the configuration options.
|
||||
type Opts struct {
|
||||
I2cAddress uint16
|
||||
Reset bool
|
||||
Mode Mode
|
||||
InterruptPadEnabled bool // If enabled, this can cause I2C failures. See documentation.
|
||||
EnableTemperatureMeasurement bool // Disable to save power.
|
||||
ParityTestEnabled bool
|
||||
TemperatureOffsetCompensation int
|
||||
}
|
||||
|
||||
// DefaultOpts are the recommended default options.
|
||||
var DefaultOpts = Opts{
|
||||
I2cAddress: I2CAddr,
|
||||
Reset: true,
|
||||
Mode: PowerDownMode,
|
||||
EnableTemperatureMeasurement: true,
|
||||
InterruptPadEnabled: false,
|
||||
ParityTestEnabled: true,
|
||||
TemperatureOffsetCompensation: 340, // As per the documentation, can be calibrated for better precision
|
||||
}
|
||||
|
||||
// Sample contains the metrics measured by the sensor
|
||||
type Sample struct {
|
||||
Bx physic.MagneticFluxDensity
|
||||
By physic.MagneticFluxDensity
|
||||
Bz physic.MagneticFluxDensity
|
||||
Temperature physic.Temperature
|
||||
}
|
||||
|
||||
// Dev is an handle to a TLV493D hall effect sensor.
|
||||
type Dev struct {
|
||||
mu sync.Mutex
|
||||
i2c i2c.Dev
|
||||
stop chan struct{}
|
||||
continuousReadWG sync.WaitGroup
|
||||
|
||||
registersBuffer []byte
|
||||
|
||||
mode Mode
|
||||
enableTemperatureMeasurement bool
|
||||
interruptPadEnabled bool
|
||||
parityTestEnabled bool
|
||||
|
||||
temperatureOffsetCompensation int
|
||||
}
|
||||
|
||||
// New creates a new TLV493D driver for a 3D hall effect sensors
|
||||
func New(i i2c.Bus, opts *Opts) (*Dev, error) {
|
||||
switch opts.I2cAddress {
|
||||
case I2CAddr, I2CAddr1:
|
||||
default:
|
||||
return nil, errors.New("TLV493D: given address not supported by device")
|
||||
}
|
||||
|
||||
d := &Dev{
|
||||
i2c: i2c.Dev{Bus: i, Addr: opts.I2cAddress},
|
||||
mode: opts.Mode,
|
||||
enableTemperatureMeasurement: opts.EnableTemperatureMeasurement,
|
||||
interruptPadEnabled: opts.InterruptPadEnabled,
|
||||
parityTestEnabled: opts.ParityTestEnabled,
|
||||
temperatureOffsetCompensation: opts.TemperatureOffsetCompensation,
|
||||
registersBuffer: make([]byte, numberOfReadRegisters),
|
||||
}
|
||||
if err := d.initialize(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// String implements conn.Resource.
|
||||
func (d *Dev) String() string {
|
||||
return "TLV493D"
|
||||
}
|
||||
|
||||
// Halt implements conn.Resource.
|
||||
func (d *Dev) Halt() error {
|
||||
// Stop any continuous read
|
||||
d.StopContinousRead()
|
||||
|
||||
return d.SetMode(PowerDownMode)
|
||||
}
|
||||
|
||||
func (d *Dev) initialize(opts *Opts) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
time.Sleep(startupDelay)
|
||||
|
||||
// Send recovery
|
||||
if err := d.i2c.Tx([]byte{commandRecovery}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Reset {
|
||||
// Reset I2C address
|
||||
var resetAddress byte = 0x00
|
||||
if d.i2c.Addr == I2CAddr1 {
|
||||
resetAddress = 0xff
|
||||
}
|
||||
if err := d.i2c.Tx([]byte{resetAddress}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Read all 10 registers and store it as the initial data
|
||||
if err := d.i2c.Tx([]byte{registerBx}, d.registersBuffer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure sensor
|
||||
return d.configure()
|
||||
}
|
||||
|
||||
func (d *Dev) configure() error {
|
||||
|
||||
// Configure sensor
|
||||
config1 := d.registersBuffer[registerFactSet1]
|
||||
config2 := d.registersBuffer[registerFactSet3]
|
||||
|
||||
// Unset parity bit first
|
||||
config1 = setBit(config1, bitParity, false)
|
||||
|
||||
// Mode
|
||||
config1 = setBit(config1, bitFastMode, d.mode.fastMode)
|
||||
config1 = setBit(config1, bitLowPowerMode, d.mode.lowPowerMode)
|
||||
config2 = setBit(config2, bitLowPowerPeriod, d.mode.lowPowerPeriod)
|
||||
|
||||
// Temperature: set to 0 to enable it
|
||||
config2 = setBit(config2, bitTemperatureMeasurement, !d.enableTemperatureMeasurement)
|
||||
|
||||
// Other configuration bits
|
||||
config1 = setBit(config1, bitInterruptPad, d.interruptPadEnabled)
|
||||
config2 = setBit(config2, bitParityTest, d.parityTestEnabled)
|
||||
|
||||
configBuffer := []byte{
|
||||
0x00,
|
||||
config1,
|
||||
d.registersBuffer[registerFactSet2],
|
||||
config2,
|
||||
}
|
||||
|
||||
// Parity: the number of bits set must be odd
|
||||
// As we unset the parity bit first, if the number of bits is currently even, we have to set it
|
||||
// to make the number of bits set odd
|
||||
configBuffer[1] = setBit(config1, 7, isNumberOfBitsEven(configBuffer))
|
||||
|
||||
if err := d.i2c.Tx(configBuffer, nil); err != nil {
|
||||
return fmt.Errorf("unable to read configuration: %#v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMode sets the power mode of the sensor
|
||||
func (d *Dev) SetMode(mode Mode) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.mode = mode
|
||||
return d.configure()
|
||||
}
|
||||
|
||||
// EnableTemperatureMeasurement controls the temperature sensor activation
|
||||
func (d *Dev) EnableTemperatureMeasurement(enable bool) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.enableTemperatureMeasurement = enable
|
||||
return d.configure()
|
||||
}
|
||||
|
||||
// EnableInterruptions controls if the sensor should send interruption of new measurement
|
||||
func (d *Dev) EnableInterruptions(enable bool) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.interruptPadEnabled = enable
|
||||
return d.configure()
|
||||
}
|
||||
|
||||
// EnableParityTest controls if the sensor should control the parity of the data transmitted
|
||||
func (d *Dev) EnableParityTest(enable bool) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.parityTestEnabled = enable
|
||||
return d.configure()
|
||||
}
|
||||
|
||||
// Read returns a sample from the last measurement of the sensor
|
||||
func (d *Dev) Read(precision Precision) (Sample, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if precision == LowPrecision {
|
||||
return d.readLowPrecision()
|
||||
}
|
||||
|
||||
return d.readHighPrecision()
|
||||
}
|
||||
|
||||
func (d *Dev) readLowPrecision() (Sample, error) {
|
||||
// The information we need is in the first 3 registers
|
||||
if err := d.i2c.Tx([]byte{registerBx}, d.registersBuffer[:numberOfFastMeasurementRegisters]); err != nil {
|
||||
return Sample{}, err
|
||||
}
|
||||
buf := d.registersBuffer
|
||||
|
||||
// The values are signed:
|
||||
// convert uint8 to int8, then convert to int to preserve the sign
|
||||
rawBx := int(int8(buf[registerBx])) << 4
|
||||
rawBy := int(int8(buf[registerBy])) << 4
|
||||
rawBz := int(int8(buf[registerBz])) << 4
|
||||
|
||||
return Sample{
|
||||
Bx: magneticFluxScaling * physic.MagneticFluxDensity(rawBx),
|
||||
By: magneticFluxScaling * physic.MagneticFluxDensity(rawBy),
|
||||
Bz: magneticFluxScaling * physic.MagneticFluxDensity(rawBz),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Dev) readHighPrecision() (Sample, error) {
|
||||
// The information we need is in the first 7 registers
|
||||
if err := d.i2c.Tx([]byte{registerBx}, d.registersBuffer[:numberOfMeasurementRegisters]); err != nil {
|
||||
return Sample{}, err
|
||||
}
|
||||
buf := d.registersBuffer
|
||||
|
||||
// The values are signed:
|
||||
// convert uint8 to int8, then convert to int to preserve the sign
|
||||
rawBx := (int(int8(buf[registerBx])) << 4) | (int(buf[registerBx2]&0xf0) >> 4)
|
||||
rawBy := (int(int8(buf[registerBy])) << 4) | int(buf[registerBx2]&0x0f)
|
||||
rawBz := (int(int8(buf[registerBz])) << 4) | int(buf[registerBz2]&0x0f)
|
||||
rawTemp := (int(int8(buf[registerTemp]&0xf0)) << 4) | int(buf[registerTemp2])
|
||||
|
||||
// Compute measurement based upon reference documentation
|
||||
temp := physic.Temperature(rawTemp-d.temperatureOffsetCompensation)*temperatureScaling + referenceTemperature
|
||||
|
||||
return Sample{
|
||||
Bx: magneticFluxScaling * physic.MagneticFluxDensity(rawBx),
|
||||
By: magneticFluxScaling * physic.MagneticFluxDensity(rawBy),
|
||||
Bz: magneticFluxScaling * physic.MagneticFluxDensity(rawBz),
|
||||
Temperature: temp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadContinuous returns a channel which will receive readings at regular intervals
|
||||
func (d *Dev) ReadContinuous(frequency physic.Frequency, precision Precision) (<-chan Sample, error) {
|
||||
// First release the current continuous reading if there is one
|
||||
d.StopContinousRead()
|
||||
reading := make(chan Sample, 16)
|
||||
d.stop = make(chan struct{})
|
||||
|
||||
// Choose the best operating mode for the sensor
|
||||
newMode, err := bestModeForFrequency(frequency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
previousMode := d.mode
|
||||
d.SetMode(newMode)
|
||||
|
||||
t := time.NewTicker(frequency.Period())
|
||||
|
||||
d.continuousReadWG.Add(1)
|
||||
|
||||
go func(s <-chan struct{}) {
|
||||
defer d.SetMode(previousMode)
|
||||
defer t.Stop()
|
||||
defer close(reading)
|
||||
defer d.continuousReadWG.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s:
|
||||
return
|
||||
case <-t.C:
|
||||
value, err := d.Read(precision)
|
||||
if err != nil {
|
||||
// In continuous mode, we'll ignore errors silently.
|
||||
continue
|
||||
}
|
||||
reading <- value
|
||||
}
|
||||
}
|
||||
}(d.stop)
|
||||
|
||||
return reading, nil
|
||||
}
|
||||
|
||||
// StopContinousRead stops a currently running continuous read
|
||||
func (d *Dev) StopContinousRead() {
|
||||
if d.stop == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.stop <- struct{}{}
|
||||
d.stop = nil
|
||||
d.continuousReadWG.Wait()
|
||||
}
|
||||
|
||||
func bestModeForFrequency(frequency physic.Frequency) (Mode, error) {
|
||||
|
||||
allowed := []*Mode{&FastMode, &LowPowerMode, &UltraLowPowerMode}
|
||||
var minAbove *Mode = nil
|
||||
|
||||
for _, m := range allowed {
|
||||
if m.measurementFrequency >= frequency {
|
||||
if minAbove == nil || minAbove.measurementFrequency > m.measurementFrequency {
|
||||
minAbove = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if minAbove == nil {
|
||||
return PowerDownMode, fmt.Errorf("frequency too high, no mode available for %s", frequency)
|
||||
}
|
||||
|
||||
return *minAbove, nil
|
||||
}
|
||||
|
||||
// Sets the bit at pos in the integer n.
|
||||
func setBit(n byte, pos uint, isSet bool) byte {
|
||||
var bit byte = (1 << pos)
|
||||
if isSet {
|
||||
return n | bit
|
||||
}
|
||||
return n &^ bit
|
||||
}
|
||||
|
||||
func isNumberOfBitsEven(array []byte) bool {
|
||||
var accumulator byte = 0
|
||||
|
||||
// The keys is to use XOR
|
||||
// 1 ^ 1 -> 0 (even)
|
||||
// 0 ^ 1 -> 1 (odd)
|
||||
// 1 ^ 0 -> 1 (odd)
|
||||
// 0 ^ 0 -> 0 (even)
|
||||
|
||||
// Combine all bytes
|
||||
for _, b := range array {
|
||||
accumulator ^= b
|
||||
}
|
||||
|
||||
// Combine adjacent bits
|
||||
accumulator ^= (accumulator >> 1)
|
||||
accumulator ^= (accumulator >> 2)
|
||||
accumulator ^= (accumulator >> 4)
|
||||
|
||||
// Parity is in the LSB
|
||||
return !((accumulator & 0x01) == 1)
|
||||
}
|
||||
@ -0,0 +1,345 @@
|
||||
// Copyright 2020 The Periph Authors. All rights reserved.
|
||||
// Use of this source code is governed under the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package tlv493d
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"periph.io/x/periph/conn/i2c/i2ctest"
|
||||
"periph.io/x/periph/conn/physic"
|
||||
)
|
||||
|
||||
func TestDev_String(t *testing.T) {
|
||||
b := i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{
|
||||
// Recovery
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0xff},
|
||||
R: []byte{},
|
||||
},
|
||||
// Reset
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{},
|
||||
},
|
||||
// Read configuration
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{0xfd, 0x2d, 0x79, 0x14, 0xab, 0x22, 0x51, 0x81, 0x4, 0x60},
|
||||
},
|
||||
// Configure
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x80, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
// Halt: power down
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x80, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
defer b.Close()
|
||||
|
||||
d, err := New(&b, &DefaultOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s := d.String(); s != "TLV493D" {
|
||||
t.Fatal(s)
|
||||
}
|
||||
if err := d.Halt(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLV493D_Read(t *testing.T) {
|
||||
b := i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{
|
||||
// Recovery
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0xff},
|
||||
R: []byte{},
|
||||
},
|
||||
// Reset
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{},
|
||||
},
|
||||
// Read configuration
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{0xfd, 0x2d, 0x79, 0x14, 0xab, 0x22, 0x51, 0x81, 0x4, 0x60},
|
||||
},
|
||||
// Configure
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x81, 0x4, 0x60},
|
||||
R: []byte{},
|
||||
},
|
||||
// Read measurements
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{0xfd, 0x2d, 0x79, 0x18, 0xbb, 0x31, 0x51},
|
||||
},
|
||||
// Halt: power down
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x80, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
defer b.Close()
|
||||
|
||||
opts := DefaultOpts
|
||||
opts.Mode = LowPowerMode
|
||||
|
||||
d, err := New(&b, &opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read values from ADC.
|
||||
reading, err := d.Read(HighPrecisionWithTemperature)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertSample(t, Sample{
|
||||
Bx: -3626 * physic.MicroTesla,
|
||||
By: 71638 * physic.MicroTesla,
|
||||
Bz: 189826 * physic.MicroTesla,
|
||||
Temperature: 294850 * physic.MilliKelvin,
|
||||
}, reading)
|
||||
|
||||
if err := d.Halt(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLV493D_ReadContinous(t *testing.T) {
|
||||
b := i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{
|
||||
// Recovery
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0xff},
|
||||
R: []byte{},
|
||||
},
|
||||
// Reset
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{},
|
||||
},
|
||||
// Read configuration
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{0xfd, 0x2d, 0x79, 0x14, 0xab, 0x22, 0x51, 0x81, 0x4, 0x60},
|
||||
},
|
||||
// Configure
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x80, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
// Configure for continuous mode
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x81, 0x4, 0x60},
|
||||
R: []byte{},
|
||||
},
|
||||
// Read measurements
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{0xfd, 0x2d, 0x79},
|
||||
},
|
||||
// Read measurements
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{0xf3, 0xd5, 0xa},
|
||||
},
|
||||
// End of continuous reading, restore previous mode
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x80, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
// Halt: power down
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x80, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
defer b.Close()
|
||||
|
||||
samples := []Sample{
|
||||
{
|
||||
Bx: -4704 * physic.MicroTesla,
|
||||
By: 70560 * physic.MicroTesla,
|
||||
Bz: 189728 * physic.MicroTesla,
|
||||
},
|
||||
{
|
||||
Bx: -20384 * physic.MicroTesla,
|
||||
By: -67424 * physic.MicroTesla,
|
||||
Bz: 15680 * physic.MicroTesla,
|
||||
},
|
||||
}
|
||||
|
||||
d, err := New(&b, &DefaultOpts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read values from sensor.
|
||||
c, err := d.ReadContinuous(100*physic.Hertz, LowPrecision)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var i = 0
|
||||
for reading := range c {
|
||||
assertSample(t, samples[i], reading)
|
||||
|
||||
i++
|
||||
if i >= len(samples) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
d.StopContinousRead()
|
||||
|
||||
if err := d.Halt(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLV493D_Configuration(t *testing.T) {
|
||||
b := i2ctest.Playback{
|
||||
Ops: []i2ctest.IO{
|
||||
// Recovery
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0xff},
|
||||
R: []byte{},
|
||||
},
|
||||
// Reset
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{},
|
||||
},
|
||||
// Read configuration
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0},
|
||||
R: []byte{0xfd, 0x2d, 0x79, 0x14, 0xab, 0x22, 0x51, 0x81, 0x4, 0x60},
|
||||
},
|
||||
// Configure
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x80, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
// Configure for UltraLowPowerMode
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x1, 0x4, 0x20},
|
||||
R: []byte{},
|
||||
},
|
||||
// Disable temperature measurement
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x81, 0x4, 0xa0},
|
||||
R: []byte{},
|
||||
},
|
||||
// Disable parity test
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x1, 0x4, 0x80},
|
||||
R: []byte{},
|
||||
},
|
||||
// Disable interruptions
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x85, 0x4, 0x80},
|
||||
R: []byte{},
|
||||
},
|
||||
// Halt: power down
|
||||
{
|
||||
Addr: 0x5e,
|
||||
W: []byte{0x0, 0x4, 0x4, 0x80},
|
||||
R: []byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
defer b.Close()
|
||||
|
||||
opts := DefaultOpts
|
||||
|
||||
d, err := New(&b, &opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Change configuration items
|
||||
err = d.SetMode(UltraLowPowerMode)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = d.EnableTemperatureMeasurement(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = d.EnableParityTest(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = d.EnableInterruptions(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := d.Halt(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSample(t *testing.T, expected Sample, actual Sample) {
|
||||
if actual.Bx != expected.Bx {
|
||||
t.Fatalf("Bx: Found %d, expected %d", actual.Bx, expected.Bx)
|
||||
}
|
||||
|
||||
if actual.By != expected.By {
|
||||
t.Fatalf("By: Found %d, expected %d", actual.By, expected.By)
|
||||
}
|
||||
|
||||
if actual.Bz != expected.Bz {
|
||||
t.Fatalf("Bz: Found %d, expected %d", actual.Bz, expected.Bz)
|
||||
}
|
||||
|
||||
if actual.Temperature != expected.Temperature {
|
||||
t.Fatalf("Temperature: Found %d, expected %d", actual.Temperature, expected.Temperature)
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue