tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/as560x/as560x.go (about)

     1  // Package as560x implements drivers for the ams AS5600/AS5601 on-axis magnetic rotary position sensors
     2  //
     3  // Product Pages:
     4  //	AS5600: https://ams.com/as5600
     5  //	AS5601: https://ams.com/as5601
     6  //
     7  // Datasheets:
     8  //	AS5600: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf
     9  //	AS5601: https://ams.com/documents/20143/36005/AS5601_DS000395_3-00.pdf
    10  //
    11  
    12  package as560x // import tinygo.org/x/drivers/ams560x
    13  
    14  import (
    15  	"errors"
    16  
    17  	"tinygo.org/x/drivers"
    18  )
    19  
    20  // Config holds the configuration for the AMS AS560x sensor devices.
    21  type Config struct {
    22  	// Address is the I2C address of the AS560x device. If left zero this will default to 0x36
    23  	Address uint8
    24  }
    25  
    26  // MagnetStrength is an enum to indicate the magnetic field strength detected by the AS560x sensors.
    27  type MagnetStrength int
    28  
    29  const (
    30  	// MagnetTooWeak indicates that the magnet strength is too weak (AGC maximum gain overflow) - move it closer
    31  	MagnetTooWeak MagnetStrength = iota - 1
    32  	// MagnetOk indicates that the magnet strength is about right.
    33  	MagnetOk
    34  	// MagnetTooStrong indicates that the magnet strength is too strong (AGC minimum gain overflow) - move it further away
    35  	MagnetTooStrong
    36  )
    37  
    38  // AngleUnit is an enum to allow the use of different units when reading/writing angles from the AS560x sensors.
    39  type AngleUnit int
    40  
    41  const (
    42  	// ANGLE_NATIVE uses the device's native angle measurement. i.e. 12-bit integer, 0 <= angle <= 0xfff (4095)
    43  	ANGLE_NATIVE AngleUnit = iota
    44  	// ANGLE_DEGREES_INT measures angles in degrees using integer arithmetic for speed. i.e. 0 <= angle < 360
    45  	ANGLE_DEGREES_INT
    46  	// ANGLE_DEGREES_FLOAT measures angles in degrees using floating point (slower). i.e. 0.0 <= angle < 360.0
    47  	ANGLE_DEGREES_FLOAT
    48  	// ANGLE_RADIANS measures angles in radians using floating point (slower). i.e. 0.0 <= angle < 2 * PI
    49  	ANGLE_RADIANS
    50  )
    51  
    52  const (
    53  	// NATIVE_ANGLE_MAX is the maximum valid value for a native angle for a AS560x device
    54  	NATIVE_ANGLE_MAX = (1 << 12) - 1 + iota
    55  	// NATIVE_ANGLE_RANGE is the number of unique values for native angles for a AS560x device
    56  	NATIVE_ANGLE_RANGE
    57  )
    58  
    59  var (
    60  	errRegisterNotFound = errors.New("Register not found")
    61  	errMaxBurnAngle     = errors.New("Max BURN_ANGLE limit reached")
    62  )
    63  
    64  // BaseDevice handles the common behaviour between AS5600 & AS5601 devices
    65  type BaseDevice struct {
    66  	bus       drivers.I2C
    67  	address   uint8
    68  	registers map[uint8]*i2cRegister
    69  	maxAngle  uint16
    70  }
    71  
    72  // newBaseDevice creates a new base device given an I2C bus.
    73  func newBaseDevice(bus drivers.I2C) BaseDevice {
    74  	// Add all 'base' registers, common to both AS5600 & AS5601
    75  	conf := newI2CRegister(CONF, 0, 0x3fff, 2, reg_read|reg_write|reg_program)
    76  	status := newI2CRegister(STATUS, 0, 0xff, 1, reg_read)
    77  	regs := map[uint8]*i2cRegister{
    78  		ZPOS:      newI2CRegister(ZPOS, 0, 0xfff, 2, reg_read|reg_write|reg_program),
    79  		CONF:      conf,
    80  		RAW_ANGLE: newI2CRegister(RAW_ANGLE, 0, 0xfff, 2, reg_read),
    81  		ANGLE:     newI2CRegister(ANGLE, 0, 0xfff, 2, reg_read),
    82  		STATUS:    status,
    83  		AGC:       newI2CRegister(AGC, 0, 0xff, 1, reg_read),
    84  		MAGNITUDE: newI2CRegister(MAGNITUDE, 0, 0xfff, 2, reg_read),
    85  		BURN:      newI2CRegister(BURN, 0, 0xff, 1, reg_write),
    86  		// Add common 'virtual registers' These are bitfields within the common registers above
    87  		// A virtual register provides a convenient way to access the fields of a registers
    88  		// by handling all of the necessary bitfield shifting and masking operations
    89  		WD:   newVirtualRegister(conf, 13, 0b1),
    90  		FTH:  newVirtualRegister(conf, 10, 0b111),
    91  		SF:   newVirtualRegister(conf, 8, 0b11),
    92  		HYST: newVirtualRegister(conf, 2, 0b11),
    93  		PM:   newVirtualRegister(conf, 0, 0b11),
    94  		MD:   newVirtualRegister(status, 5, 0b1),
    95  		ML:   newVirtualRegister(status, 4, 0b1),
    96  		MH:   newVirtualRegister(status, 3, 0b1),
    97  	}
    98  	return BaseDevice{bus, DefaultAddress, regs, NATIVE_ANGLE_RANGE}
    99  }
   100  
   101  // Configure sets up the AMS AS560x sensor device with the given configuration.
   102  func (d *BaseDevice) Configure(cfg Config) {
   103  	if cfg.Address == 0 {
   104  		cfg.Address = DefaultAddress
   105  	}
   106  	d.address = cfg.Address
   107  }
   108  
   109  // ReadRegister reads the value for the given register from the AS560x device via I2C
   110  func (d *BaseDevice) ReadRegister(address uint8) (uint16, error) {
   111  	reg, ok := d.registers[address]
   112  	if !ok {
   113  		return 0, errRegisterNotFound
   114  	}
   115  	return reg.read(d.bus, d.address)
   116  }
   117  
   118  // WriteRegister writes the given value for the given register to the AS560x device via I2C
   119  func (d *BaseDevice) WriteRegister(address uint8, value uint16) error {
   120  	reg, ok := d.registers[address]
   121  	if !ok {
   122  		return errRegisterNotFound
   123  	}
   124  	return reg.write(d.bus, d.address, value)
   125  }
   126  
   127  // GetZeroPosition returns the 'zero position' (ZPOS) in various units
   128  func (d *BaseDevice) GetZeroPosition(units AngleUnit) (uint16, float32, error) {
   129  	zpos, err := d.ReadRegister(ZPOS)
   130  	if nil != err {
   131  		return 0, 0.0, err
   132  	}
   133  	// Convert to requested units
   134  	i, f := convertFromNativeAngle(zpos, NATIVE_ANGLE_RANGE, units)
   135  	return i, f, nil
   136  }
   137  
   138  // SetZeroPosition sets the 'zero position' (ZPOS) in various units
   139  func (d *BaseDevice) SetZeroPosition(zpos float32, units AngleUnit) error {
   140  	return d.WriteRegister(ZPOS, convertToNativeAngle(zpos, units))
   141  }
   142  
   143  // RawAngle reads the (unscaled & unadjusted) RAW_ANGLE register in various units
   144  func (d *BaseDevice) RawAngle(units AngleUnit) (uint16, float32, error) {
   145  	angle, err := d.ReadRegister(RAW_ANGLE)
   146  	if nil != err {
   147  		return 0, 0.0, err
   148  	}
   149  	// Convert to requested units
   150  	i, f := convertFromNativeAngle(angle, NATIVE_ANGLE_RANGE, units)
   151  	return i, f, nil
   152  }
   153  
   154  // Angle reads the (scaled & adjusted) ANGLE register in various units
   155  func (d *BaseDevice) Angle(units AngleUnit) (uint16, float32, error) {
   156  	// ZPOS enables setting the 'zero position' of the device to any RAW_ANGLE value
   157  	// ANGLE is RAW_ANGLE adjusted relative to ZPOS.
   158  	angle, err := d.ReadRegister(ANGLE)
   159  	if nil != err {
   160  		return 0, 0.0, err
   161  	}
   162  	// Convert to requested units
   163  	i, f := convertFromNativeAngle(angle, d.maxAngle, units)
   164  	return i, f, nil
   165  }
   166  
   167  // MagnetStatus reads the STATUS register and reports magnet position characteristics
   168  func (d *BaseDevice) MagnetStatus() (detected bool, strength MagnetStrength, err error) {
   169  	status, err := d.ReadRegister(STATUS)
   170  	if nil != err {
   171  		return false, MagnetOk, err
   172  	}
   173  	detected = (status & STATUS_MD) != 0
   174  	strength = MagnetOk
   175  	if (status & STATUS_ML) != 0 {
   176  		strength = MagnetTooWeak
   177  	} else if (status & STATUS_MH) != 0 {
   178  		strength = MagnetTooStrong
   179  	}
   180  	return
   181  }
   182  
   183  // Burn is a convenience method to program the device permanently by writing to the BURN register (limited number of times use!)
   184  func (d *BaseDevice) Burn(burnCmd BURN_CMD) error {
   185  	if BURN_ANGLE == burnCmd {
   186  		// BURN_ANGLE can only be executed up to 3 times.
   187  		// We can check this in advance by reading ZMCO before writing to the BURN register.
   188  		numBurns, err := d.ReadRegister(ZMCO)
   189  		if nil != err {
   190  			return err
   191  		}
   192  		if numBurns >= BURN_ANGLE_COUNT_MAX {
   193  			// We're outta BURNs :(
   194  			return errMaxBurnAngle
   195  		}
   196  	}
   197  	return d.WriteRegister(BURN, uint16(burnCmd))
   198  }