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

     1  //	Product: https://ams.com/as5600
     2  //	Datasheet: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf
     3  
     4  package as560x // import tinygo.org/x/drivers/ams560x
     5  
     6  import (
     7  	"time"
     8  
     9  	"tinygo.org/x/drivers"
    10  )
    11  
    12  // AS5600 includes MPOS & MANG in addition to ZPOS to set a 'narrower angle range'
    13  // ZPOS enables setting the 'zero position' of the device to any RAW_ANGLE value.
    14  // MPOS ('max position') & MANG 'max angle' enable a 'partial range' on the AS5600.
    15  // The value in ANGLE is scaled & adjusted by the device according to ZPOS and MPOS/MANG.
    16  // The entire 12-bit range is 'compressed' into the RAW_ANGLE range of ZPOS->MPOS
    17  // (or ZPOS->ZPOS+MANG) thus enabling a higher resolution for a partial range.
    18  // if ZPOS > MPOS (or ZPOS + MANG > 4095) i.e. the incremental range 'crosses zero'
    19  // then the device will automatically compensate for the correct range.
    20  // For RAW_ANGLE values outside of the partial range, ANGLE will be 'capped' at either
    21  // 0 or 4095, depending on 'which end of the partial range is closer.'
    22  
    23  // AS5600Device represents an ams AS5600 device driver accessed over I2C
    24  type AS5600Device struct {
    25  	// promote BaseDevice
    26  	BaseDevice
    27  }
    28  
    29  // NewAS5600 creates a new AS5600Device given an I2C bus
    30  func NewAS5600(bus drivers.I2C) AS5600Device {
    31  	// Create base device
    32  	baseDev := newBaseDevice(bus)
    33  	// Add AS5600 specific registers
    34  	baseDev.registers[MPOS] = newI2CRegister(MPOS, 0, 0xfff, 2, reg_read|reg_write|reg_program)
    35  	baseDev.registers[MANG] = newI2CRegister(MANG, 0, 0xfff, 2, reg_read|reg_write|reg_program)
    36  	// Add AS5600 specific 'virtual registers'
    37  	conf, ok := baseDev.registers[CONF]
    38  	if ok {
    39  		baseDev.registers[PWMF] = newVirtualRegister(conf, 6, 0b11)
    40  		baseDev.registers[OUTS] = newVirtualRegister(conf, 4, 0b11)
    41  	}
    42  	// Return the device
    43  	return AS5600Device{baseDev}
    44  }
    45  
    46  // Configure sets up the AMS AS5600 sensor device with the given configuration.
    47  func (d *AS5600Device) Configure(cfg Config) error {
    48  	// Call the BaseDevice method to do the actual Configure
    49  	d.BaseDevice.Configure(cfg)
    50  	// For AS5600 devices we need to calculate the maxAngle on startup from ZPOS/MPOS/MANG
    51  	// These could have been permanently BURN'ed (by writing BURN register with BURN_ANGLE/BURN_SETTING)
    52  	// or may have already been written in previous runs without a power cycle since.
    53  	mpos, err := d.ReadRegister(MPOS)
    54  	if nil != err {
    55  		return err
    56  	}
    57  	mang, err := d.ReadRegister(MANG)
    58  	if nil != err {
    59  		return err
    60  	}
    61  	// Read ZPOS for side effect of caching only so that next calculateEffectiveMaxAngle() can't fail
    62  	if _, err = d.ReadRegister(ZPOS); nil != err {
    63  		return err
    64  	}
    65  	if mpos != 0 {
    66  		// If MPOS is set, use MPOS regardless of MANG
    67  		err = d.calculateEffectiveMaxAngle(MPOS, mpos)
    68  	} else if mang != 0 {
    69  		// If MANG is set and MPOS == 0, use MANG
    70  		err = d.calculateEffectiveMaxAngle(MANG, mang)
    71  	} else {
    72  		// if neither is set, we have no narrow range
    73  		d.maxAngle = NATIVE_ANGLE_RANGE
    74  	}
    75  	return err
    76  }
    77  
    78  // calculateEffectiveMaxAngle calculates d.maxAngle after one of ZPOS/MPOS/MANG have been written
    79  func (d *AS5600Device) calculateEffectiveMaxAngle(register uint8, value uint16) error {
    80  
    81  	var zpos, mpos uint16 = 0, 0
    82  	var err error = nil
    83  
    84  	switch register {
    85  	case MANG:
    86  		d.maxAngle = value // The easy case
    87  		return nil
    88  	case ZPOS:
    89  		zpos = value
    90  		mpos, err = d.ReadRegister(MPOS)
    91  	case MPOS:
    92  		mpos = value
    93  		zpos, err = d.ReadRegister(ZPOS)
    94  	default:
    95  		panic("calculateEffectiveMaxAngle() can only work from ZPOS, MPOS or MANG")
    96  	}
    97  
    98  	if nil != err {
    99  		return err
   100  	}
   101  	// MANG is effectively MPOS-ZPOS
   102  	mang := int(mpos) - int(zpos)
   103  	// correct for mpos < zpos
   104  	if mang < 0 {
   105  		mang += NATIVE_ANGLE_RANGE
   106  	}
   107  	d.maxAngle = uint16(mang)
   108  	return nil
   109  }
   110  
   111  // WriteRegister writes the given value for the given register to the AS560x device via I2C
   112  func (d *AS5600Device) WriteRegister(address uint8, value uint16) error {
   113  	// Call the BaseDevice method to do the actual write
   114  	if err := d.BaseDevice.WriteRegister(address, value); err != nil {
   115  		return err
   116  	}
   117  	// When either ZPOS/MANG/MPOS are set we need to recalculate maxAngle
   118  	// We also may need to invalidate some cached values for the other two registers
   119  	recalc := false
   120  	switch address {
   121  	case ZPOS:
   122  		// Setting a new ZPOS invalidates MPOS but not MANG
   123  		d.registers[MPOS].invalidate()
   124  		recalc = true
   125  	case MPOS:
   126  		// Setting a new MPOS invalidates MANG but not ZPOS
   127  		d.registers[MANG].invalidate()
   128  		recalc = true
   129  	case MANG:
   130  		// Setting a new MANG invalidates MPOS but not ZPOS
   131  		d.registers[MPOS].invalidate()
   132  		recalc = true
   133  	}
   134  	if recalc {
   135  		// Datasheet tells us to wait at least 1ms before reading back
   136  		time.Sleep(time.Millisecond * 10) // conservative wait
   137  		return d.calculateEffectiveMaxAngle(address, value)
   138  	}
   139  	return nil
   140  }
   141  
   142  // GetMaxPosition returns the 'max position' (MPOS) in different units
   143  func (d *AS5600Device) GetMaxPosition(units AngleUnit) (uint16, float32, error) {
   144  	mpos, err := d.ReadRegister(MPOS)
   145  	if nil != err {
   146  		return 0, 0.0, err
   147  	}
   148  	// Convert to requested units
   149  	i, f := convertFromNativeAngle(mpos, NATIVE_ANGLE_RANGE, units)
   150  	return i, f, nil
   151  }
   152  
   153  // SetMaxPosition sets the 'max position' (MPOS) in different units
   154  func (d *AS5600Device) SetMaxPosition(mpos float32, units AngleUnit) error {
   155  	return d.WriteRegister(MPOS, convertToNativeAngle(mpos, units))
   156  }
   157  
   158  // GetMaxAngle returns the 'max position' (MANG) in different units
   159  func (d *AS5600Device) GetMaxAngle(units AngleUnit) (uint16, float32, error) {
   160  	mang, err := d.ReadRegister(MANG)
   161  	if nil != err {
   162  		return 0, 0.0, err
   163  	}
   164  	// Convert to requested units
   165  	i, f := convertFromNativeAngle(mang, NATIVE_ANGLE_RANGE, units)
   166  	return i, f, nil
   167  }
   168  
   169  // SetMaxAngle sets the 'max angle' (MANG) in different units
   170  func (d *AS5600Device) SetMaxAngle(mang float32, units AngleUnit) error {
   171  	return d.WriteRegister(MANG, convertToNativeAngle(mang, units))
   172  }