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 }