mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
199 lines
6.8 KiB
Go
199 lines
6.8 KiB
Go
// Package as560x implements drivers for the ams AS5600/AS5601 on-axis magnetic rotary position sensors
|
|
//
|
|
// Product Pages:
|
|
// AS5600: https://ams.com/as5600
|
|
// AS5601: https://ams.com/as5601
|
|
//
|
|
// Datasheets:
|
|
// AS5600: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf
|
|
// AS5601: https://ams.com/documents/20143/36005/AS5601_DS000395_3-00.pdf
|
|
//
|
|
|
|
package as560x // import tinygo.org/x/drivers/ams560x
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"tinygo.org/x/drivers"
|
|
)
|
|
|
|
// Config holds the configuration for the AMS AS560x sensor devices.
|
|
type Config struct {
|
|
// Address is the I2C address of the AS560x device. If left zero this will default to 0x36
|
|
Address uint8
|
|
}
|
|
|
|
// MagnetStrength is an enum to indicate the magnetic field strength detected by the AS560x sensors.
|
|
type MagnetStrength int
|
|
|
|
const (
|
|
// MagnetTooWeak indicates that the magnet strength is too weak (AGC maximum gain overflow) - move it closer
|
|
MagnetTooWeak MagnetStrength = iota - 1
|
|
// MagnetOk indicates that the magnet strength is about right.
|
|
MagnetOk
|
|
// MagnetTooStrong indicates that the magnet strength is too strong (AGC minimum gain overflow) - move it further away
|
|
MagnetTooStrong
|
|
)
|
|
|
|
// AngleUnit is an enum to allow the use of different units when reading/writing angles from the AS560x sensors.
|
|
type AngleUnit int
|
|
|
|
const (
|
|
// ANGLE_NATIVE uses the device's native angle measurement. i.e. 12-bit integer, 0 <= angle <= 0xfff (4095)
|
|
ANGLE_NATIVE AngleUnit = iota
|
|
// ANGLE_DEGREES_INT measures angles in degrees using integer arithmetic for speed. i.e. 0 <= angle < 360
|
|
ANGLE_DEGREES_INT
|
|
// ANGLE_DEGREES_FLOAT measures angles in degrees using floating point (slower). i.e. 0.0 <= angle < 360.0
|
|
ANGLE_DEGREES_FLOAT
|
|
// ANGLE_RADIANS measures angles in radians using floating point (slower). i.e. 0.0 <= angle < 2 * PI
|
|
ANGLE_RADIANS
|
|
)
|
|
|
|
const (
|
|
// NATIVE_ANGLE_MAX is the maximum valid value for a native angle for a AS560x device
|
|
NATIVE_ANGLE_MAX = (1 << 12) - 1 + iota
|
|
// NATIVE_ANGLE_RANGE is the number of unique values for native angles for a AS560x device
|
|
NATIVE_ANGLE_RANGE
|
|
)
|
|
|
|
var (
|
|
errRegisterNotFound = errors.New("Register not found")
|
|
errMaxBurnAngle = errors.New("Max BURN_ANGLE limit reached")
|
|
)
|
|
|
|
// BaseDevice handles the common behaviour between AS5600 & AS5601 devices
|
|
type BaseDevice struct {
|
|
bus drivers.I2C
|
|
address uint8
|
|
registers map[uint8]*i2cRegister
|
|
maxAngle uint16
|
|
}
|
|
|
|
// newBaseDevice creates a new base device given an I2C bus.
|
|
func newBaseDevice(bus drivers.I2C) BaseDevice {
|
|
// Add all 'base' registers, common to both AS5600 & AS5601
|
|
conf := newI2CRegister(CONF, 0, 0x3fff, 2, reg_read|reg_write|reg_program)
|
|
status := newI2CRegister(STATUS, 0, 0xff, 1, reg_read)
|
|
regs := map[uint8]*i2cRegister{
|
|
ZPOS: newI2CRegister(ZPOS, 0, 0xfff, 2, reg_read|reg_write|reg_program),
|
|
CONF: conf,
|
|
RAW_ANGLE: newI2CRegister(RAW_ANGLE, 0, 0xfff, 2, reg_read),
|
|
ANGLE: newI2CRegister(ANGLE, 0, 0xfff, 2, reg_read),
|
|
STATUS: status,
|
|
AGC: newI2CRegister(AGC, 0, 0xff, 1, reg_read),
|
|
MAGNITUDE: newI2CRegister(MAGNITUDE, 0, 0xfff, 2, reg_read),
|
|
BURN: newI2CRegister(BURN, 0, 0xff, 1, reg_write),
|
|
// Add common 'virtual registers' These are bitfields within the common registers above
|
|
// A virtual register provides a convenient way to access the fields of a registers
|
|
// by handling all of the necessary bitfield shifting and masking operations
|
|
WD: newVirtualRegister(conf, 13, 0b1),
|
|
FTH: newVirtualRegister(conf, 10, 0b111),
|
|
SF: newVirtualRegister(conf, 8, 0b11),
|
|
HYST: newVirtualRegister(conf, 2, 0b11),
|
|
PM: newVirtualRegister(conf, 0, 0b11),
|
|
MD: newVirtualRegister(status, 5, 0b1),
|
|
ML: newVirtualRegister(status, 4, 0b1),
|
|
MH: newVirtualRegister(status, 3, 0b1),
|
|
}
|
|
return BaseDevice{bus, DefaultAddress, regs, NATIVE_ANGLE_RANGE}
|
|
}
|
|
|
|
// Configure sets up the AMS AS560x sensor device with the given configuration.
|
|
func (d *BaseDevice) Configure(cfg Config) {
|
|
if cfg.Address == 0 {
|
|
cfg.Address = DefaultAddress
|
|
}
|
|
d.address = cfg.Address
|
|
}
|
|
|
|
// ReadRegister reads the value for the given register from the AS560x device via I2C
|
|
func (d *BaseDevice) ReadRegister(address uint8) (uint16, error) {
|
|
reg, ok := d.registers[address]
|
|
if !ok {
|
|
return 0, errRegisterNotFound
|
|
}
|
|
return reg.read(d.bus, d.address)
|
|
}
|
|
|
|
// WriteRegister writes the given value for the given register to the AS560x device via I2C
|
|
func (d *BaseDevice) WriteRegister(address uint8, value uint16) error {
|
|
reg, ok := d.registers[address]
|
|
if !ok {
|
|
return errRegisterNotFound
|
|
}
|
|
return reg.write(d.bus, d.address, value)
|
|
}
|
|
|
|
// GetZeroPosition returns the 'zero position' (ZPOS) in various units
|
|
func (d *BaseDevice) GetZeroPosition(units AngleUnit) (uint16, float32, error) {
|
|
zpos, err := d.ReadRegister(ZPOS)
|
|
if nil != err {
|
|
return 0, 0.0, err
|
|
}
|
|
// Convert to requested units
|
|
i, f := convertFromNativeAngle(zpos, NATIVE_ANGLE_RANGE, units)
|
|
return i, f, nil
|
|
}
|
|
|
|
// SetZeroPosition sets the 'zero position' (ZPOS) in various units
|
|
func (d *BaseDevice) SetZeroPosition(zpos float32, units AngleUnit) error {
|
|
return d.WriteRegister(ZPOS, convertToNativeAngle(zpos, units))
|
|
}
|
|
|
|
// RawAngle reads the (unscaled & unadjusted) RAW_ANGLE register in various units
|
|
func (d *BaseDevice) RawAngle(units AngleUnit) (uint16, float32, error) {
|
|
angle, err := d.ReadRegister(RAW_ANGLE)
|
|
if nil != err {
|
|
return 0, 0.0, err
|
|
}
|
|
// Convert to requested units
|
|
i, f := convertFromNativeAngle(angle, NATIVE_ANGLE_RANGE, units)
|
|
return i, f, nil
|
|
}
|
|
|
|
// Angle reads the (scaled & adjusted) ANGLE register in various units
|
|
func (d *BaseDevice) Angle(units AngleUnit) (uint16, float32, error) {
|
|
// ZPOS enables setting the 'zero position' of the device to any RAW_ANGLE value
|
|
// ANGLE is RAW_ANGLE adjusted relative to ZPOS.
|
|
angle, err := d.ReadRegister(ANGLE)
|
|
if nil != err {
|
|
return 0, 0.0, err
|
|
}
|
|
// Convert to requested units
|
|
i, f := convertFromNativeAngle(angle, d.maxAngle, units)
|
|
return i, f, nil
|
|
}
|
|
|
|
// MagnetStatus reads the STATUS register and reports magnet position characteristics
|
|
func (d *BaseDevice) MagnetStatus() (detected bool, strength MagnetStrength, err error) {
|
|
status, err := d.ReadRegister(STATUS)
|
|
if nil != err {
|
|
return false, MagnetOk, err
|
|
}
|
|
detected = (status & STATUS_MD) != 0
|
|
strength = MagnetOk
|
|
if (status & STATUS_ML) != 0 {
|
|
strength = MagnetTooWeak
|
|
} else if (status & STATUS_MH) != 0 {
|
|
strength = MagnetTooStrong
|
|
}
|
|
return
|
|
}
|
|
|
|
// Burn is a convenience method to program the device permanently by writing to the BURN register (limited number of times use!)
|
|
func (d *BaseDevice) Burn(burnCmd BURN_CMD) error {
|
|
if BURN_ANGLE == burnCmd {
|
|
// BURN_ANGLE can only be executed up to 3 times.
|
|
// We can check this in advance by reading ZMCO before writing to the BURN register.
|
|
numBurns, err := d.ReadRegister(ZMCO)
|
|
if nil != err {
|
|
return err
|
|
}
|
|
if numBurns >= BURN_ANGLE_COUNT_MAX {
|
|
// We're outta BURNs :(
|
|
return errMaxBurnAngle
|
|
}
|
|
}
|
|
return d.WriteRegister(BURN, uint16(burnCmd))
|
|
}
|