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

     1  // Package apds9960 implements a driver for APDS-9960,
     2  // a digital proximity, ambient light, RGB and gesture sensor.
     3  //
     4  // Datasheet: https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf
     5  package apds9960
     6  
     7  import (
     8  	"time"
     9  
    10  	"tinygo.org/x/drivers"
    11  	"tinygo.org/x/drivers/internal/legacy"
    12  )
    13  
    14  // Device wraps an I2C connection to a APDS-9960 device.
    15  type Device struct {
    16  	bus     drivers.I2C
    17  	Address uint8
    18  	mode    uint8
    19  	gesture gestureData
    20  }
    21  
    22  // Configuration for APDS-9960 device.
    23  type Configuration struct {
    24  	ProximityPulseLength uint8
    25  	ProximityPulseCount  uint8
    26  	GesturePulseLength   uint8
    27  	GesturePulseCount    uint8
    28  	ProximityGain        uint8
    29  	GestureGain          uint8
    30  	ColorGain            uint8
    31  	ADCIntegrationCycles uint16
    32  	LEDBoost             uint16
    33  	threshold            uint8
    34  	sensitivity          uint8
    35  }
    36  
    37  // for gesture-related data
    38  type gestureData struct {
    39  	detected    uint8
    40  	threshold   uint8
    41  	sensitivity uint8
    42  	gXDelta     int16
    43  	gYDelta     int16
    44  	gXPrevDelta int16
    45  	gYPrevDelta int16
    46  	received    bool
    47  }
    48  
    49  // for enabling various device function
    50  type enableConfig struct {
    51  	GEN  bool
    52  	PIEN bool
    53  	AIEN bool
    54  	WEN  bool
    55  	PEN  bool
    56  	AEN  bool
    57  	PON  bool
    58  }
    59  
    60  // New creates a new APDS-9960 connection. The I2C bus must already be
    61  // configured.
    62  //
    63  // This function only creates the Device object, it does not touch the device.
    64  func New(bus drivers.I2C) Device {
    65  	return Device{bus: bus, Address: ADPS9960_ADDRESS, mode: MODE_NONE}
    66  }
    67  
    68  // Connected returns whether APDS-9960 has been found.
    69  // It does a "who am I" request and checks the response.
    70  func (d *Device) Connected() bool {
    71  	data := []byte{0}
    72  	legacy.ReadRegister(d.bus, d.Address, APDS9960_ID_REG, data)
    73  	return data[0] == 0xAB
    74  }
    75  
    76  // GetMode returns current engine mode
    77  func (d *Device) GetMode() uint8 {
    78  	return d.mode
    79  }
    80  
    81  // DisableAll turns off the device and all functions
    82  func (d *Device) DisableAll() {
    83  	d.enable(enableConfig{})
    84  	legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF4_REG, []byte{0x00})
    85  	d.mode = MODE_NONE
    86  	d.gesture.detected = GESTURE_NONE
    87  }
    88  
    89  // SetProximityPulse sets proximity pulse length (4, 8, 16, 32) and count (1~64)
    90  // default: 16, 64
    91  func (d *Device) SetProximityPulse(length, count uint8) {
    92  	legacy.WriteRegister(d.bus, d.Address, APDS9960_PPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
    93  }
    94  
    95  // SetGesturePulse sets gesture pulse length (4, 8, 16, 32) and count (1~64)
    96  // default: 16, 64
    97  func (d *Device) SetGesturePulse(length, count uint8) {
    98  	legacy.WriteRegister(d.bus, d.Address, APDS9960_GPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
    99  }
   100  
   101  // SetADCIntegrationCycles sets ALS/color ADC internal integration cycles (1~256, 1 cycle = 2.78 ms)
   102  // default: 4 (~10 ms)
   103  func (d *Device) SetADCIntegrationCycles(cycles uint16) {
   104  	if cycles > 256 {
   105  		cycles = 256
   106  	}
   107  	legacy.WriteRegister(d.bus, d.Address, APDS9960_ATIME_REG, []byte{uint8(256 - cycles)})
   108  }
   109  
   110  // SetGains sets proximity/gesture gain (1, 2, 4, 8x) and ALS/color gain (1, 4, 16, 64x)
   111  // default: 1, 1, 4
   112  func (d *Device) SetGains(proximityGain, gestureGain, colorGain uint8) {
   113  	legacy.WriteRegister(d.bus, d.Address, APDS9960_CONTROL_REG, []byte{getProximityGain(proximityGain)<<2 | getALSGain(colorGain)})
   114  	legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF2_REG, []byte{getProximityGain(gestureGain) << 5})
   115  }
   116  
   117  // LEDBoost sets proximity and gesture LED current level (100, 150, 200, 300 (%))
   118  // default: 100
   119  func (d *Device) LEDBoost(percent uint16) {
   120  	var v uint8
   121  	switch percent {
   122  	case 100:
   123  		v = 0
   124  	case 150:
   125  		v = 1
   126  	case 200:
   127  		v = 2
   128  	case 300:
   129  		v = 3
   130  	}
   131  	legacy.WriteRegister(d.bus, d.Address, APDS9960_CONFIG2_REG, []byte{0x01 | v<<4})
   132  }
   133  
   134  // Setthreshold sets threshold (0~255) for detecting gestures
   135  // default: 30
   136  func (d *Device) Setthreshold(t uint8) {
   137  	d.gesture.threshold = t
   138  }
   139  
   140  // Setsensitivity sets sensivity (0~100) for detecting gestures
   141  // default: 20
   142  func (d *Device) Setsensitivity(s uint8) {
   143  	if s > 100 {
   144  		s = 100
   145  	}
   146  	d.gesture.sensitivity = 100 - s
   147  }
   148  
   149  // EnableProximity starts the proximity engine
   150  func (d *Device) EnableProximity() {
   151  	if d.mode != MODE_NONE {
   152  		d.DisableAll()
   153  	}
   154  	d.enable(enableConfig{PON: true, PEN: true, WEN: true})
   155  	d.mode = MODE_PROXIMITY
   156  }
   157  
   158  // ProximityAvailable reports if proximity data is available
   159  func (d *Device) ProximityAvailable() bool {
   160  	if d.mode == MODE_PROXIMITY && d.readStatus("PVALID") {
   161  		return true
   162  	}
   163  	return false
   164  }
   165  
   166  // ReadProximity reads proximity data (0~255)
   167  func (d *Device) ReadProximity() (proximity int32) {
   168  	if d.mode != MODE_PROXIMITY {
   169  		return 0
   170  	}
   171  	data := []byte{0}
   172  	legacy.ReadRegister(d.bus, d.Address, APDS9960_PDATA_REG, data)
   173  	return 255 - int32(data[0])
   174  }
   175  
   176  // EnableColor starts the color engine
   177  func (d *Device) EnableColor() {
   178  	if d.mode != MODE_NONE {
   179  		d.DisableAll()
   180  	}
   181  	d.enable(enableConfig{PON: true, AEN: true, WEN: true})
   182  	d.mode = MODE_COLOR
   183  }
   184  
   185  // ColorAvailable reports if color data is available
   186  func (d *Device) ColorAvailable() bool {
   187  	if d.mode == MODE_COLOR && d.readStatus("AVALID") {
   188  		return true
   189  	}
   190  	return false
   191  }
   192  
   193  // ReadColor reads color data (red, green, blue, clear color/brightness)
   194  func (d *Device) ReadColor() (r int32, g int32, b int32, clear int32) {
   195  	if d.mode != MODE_COLOR {
   196  		return
   197  	}
   198  	data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
   199  	legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAL_REG, data[:1])
   200  	legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAH_REG, data[1:2])
   201  	legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAL_REG, data[2:3])
   202  	legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAH_REG, data[3:4])
   203  	legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAL_REG, data[4:5])
   204  	legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAH_REG, data[5:6])
   205  	legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAL_REG, data[6:7])
   206  	legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAH_REG, data[7:])
   207  	clear = int32(uint16(data[1])<<8 | uint16(data[0]))
   208  	r = int32(uint16(data[3])<<8 | uint16(data[2]))
   209  	g = int32(uint16(data[5])<<8 | uint16(data[4]))
   210  	b = int32(uint16(data[7])<<8 | uint16(data[6]))
   211  	return
   212  }
   213  
   214  // EnableGesture starts the gesture engine
   215  func (d *Device) EnableGesture() {
   216  	if d.mode != MODE_NONE {
   217  		d.DisableAll()
   218  	}
   219  	d.enable(enableConfig{PON: true, PEN: true, GEN: true, WEN: true})
   220  	d.mode = MODE_GESTURE
   221  	d.gesture.detected = GESTURE_NONE
   222  	d.gesture.gXDelta = 0
   223  	d.gesture.gYDelta = 0
   224  	d.gesture.gXPrevDelta = 0
   225  	d.gesture.gYPrevDelta = 0
   226  	d.gesture.received = false
   227  }
   228  
   229  // GestureAvailable reports if gesture data is available
   230  func (d *Device) GestureAvailable() bool {
   231  	if d.mode != MODE_GESTURE {
   232  		return false
   233  	}
   234  
   235  	data := []byte{0, 0, 0, 0}
   236  
   237  	// check GVALID
   238  	legacy.ReadRegister(d.bus, d.Address, APDS9960_GSTATUS_REG, data[:1])
   239  	if data[0]&0x01 == 0 {
   240  		return false
   241  	}
   242  
   243  	// get number of data sets available in FIFO
   244  	legacy.ReadRegister(d.bus, d.Address, APDS9960_GFLVL_REG, data[:1])
   245  	availableDataSets := data[0]
   246  	if availableDataSets == 0 {
   247  		return false
   248  	}
   249  
   250  	// read up, down, left and right proximity data from FIFO
   251  	var dataSets [32][4]uint8
   252  	for i := uint8(0); i < availableDataSets; i++ {
   253  		legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_U_REG, data[:1])
   254  		legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_D_REG, data[1:2])
   255  		legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_L_REG, data[2:3])
   256  		legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_R_REG, data[3:4])
   257  		for j := uint8(0); j < 4; j++ {
   258  			dataSets[i][j] = data[j]
   259  		}
   260  	}
   261  
   262  	// gesture detection process
   263  	d.gesture.detected = GESTURE_NONE
   264  	for i := uint8(0); i < availableDataSets; i++ {
   265  		U := dataSets[i][0]
   266  		D := dataSets[i][1]
   267  		L := dataSets[i][2]
   268  		R := dataSets[i][3]
   269  
   270  		// if all readings fall below threshold, it's possible that
   271  		// a movement's just been made
   272  		if U < d.gesture.threshold && D < d.gesture.threshold && L < d.gesture.threshold && R < d.gesture.threshold {
   273  			d.gesture.received = true
   274  			// if there were movement in the previous step (including the last data sets)
   275  			if d.gesture.gXPrevDelta != 0 && d.gesture.gYPrevDelta != 0 {
   276  				totalX := d.gesture.gXPrevDelta - d.gesture.gXDelta
   277  				totalY := d.gesture.gYPrevDelta - d.gesture.gYDelta
   278  				// if previous and current movement are in opposite directions (pass through one led then next)
   279  				// and the difference is big enough, the gesture is recorded
   280  				switch {
   281  				case totalX < -int16(d.gesture.sensitivity):
   282  					d.gesture.detected = GESTURE_LEFT
   283  				case totalX > int16(d.gesture.sensitivity):
   284  					d.gesture.detected = GESTURE_RIGHT
   285  				case totalY > int16(d.gesture.sensitivity):
   286  					d.gesture.detected = GESTURE_DOWN
   287  				case totalY < -int16(d.gesture.sensitivity):
   288  					d.gesture.detected = GESTURE_UP
   289  				}
   290  				d.gesture.gXDelta = 0
   291  				d.gesture.gYDelta = 0
   292  				d.gesture.gXPrevDelta = 0
   293  				d.gesture.gYPrevDelta = 0
   294  			}
   295  			continue
   296  		}
   297  
   298  		// recording current movement
   299  		d.gesture.gXDelta = int16(R) - int16(L)
   300  		d.gesture.gYDelta = int16(D) - int16(U)
   301  		if d.gesture.received {
   302  			d.gesture.received = false
   303  			d.gesture.gXPrevDelta = d.gesture.gXDelta
   304  			d.gesture.gYPrevDelta = d.gesture.gYDelta
   305  		}
   306  	}
   307  
   308  	return d.gesture.detected != GESTURE_NONE
   309  }
   310  
   311  // ReadGesture reads last gesture data
   312  func (d *Device) ReadGesture() (gesture int32) {
   313  	return int32(d.gesture.detected)
   314  }
   315  
   316  // private functions
   317  
   318  func (d *Device) configureDevice(cfg Configuration) {
   319  	d.DisableAll() // turn off everything
   320  
   321  	// "default" settings
   322  	if cfg.ProximityPulseLength == 0 {
   323  		cfg.ProximityPulseLength = 16
   324  	}
   325  	if cfg.ProximityPulseCount == 0 {
   326  		cfg.ProximityPulseCount = 64
   327  	}
   328  	if cfg.GesturePulseLength == 0 {
   329  		cfg.GesturePulseLength = 16
   330  	}
   331  	if cfg.GesturePulseCount == 0 {
   332  		cfg.GesturePulseCount = 64
   333  	}
   334  	if cfg.ProximityGain == 0 {
   335  		cfg.ProximityGain = 1
   336  	}
   337  	if cfg.GestureGain == 0 {
   338  		cfg.GestureGain = 1
   339  	}
   340  	if cfg.ColorGain == 0 {
   341  		cfg.ColorGain = 4
   342  	}
   343  	if cfg.ADCIntegrationCycles == 0 {
   344  		cfg.ADCIntegrationCycles = 4
   345  	}
   346  	if cfg.threshold == 0 {
   347  		d.gesture.threshold = 30
   348  	}
   349  	if cfg.sensitivity == 0 {
   350  		d.gesture.sensitivity = 20
   351  	}
   352  
   353  	d.SetProximityPulse(cfg.ProximityPulseLength, cfg.ProximityPulseCount)
   354  	d.SetGesturePulse(cfg.GesturePulseLength, cfg.GesturePulseCount)
   355  	d.SetGains(cfg.ProximityGain, cfg.GestureGain, cfg.ColorGain)
   356  	d.SetADCIntegrationCycles(cfg.ADCIntegrationCycles)
   357  
   358  	if cfg.LEDBoost > 0 {
   359  		d.LEDBoost(cfg.LEDBoost)
   360  	}
   361  }
   362  
   363  func (d *Device) enable(cfg enableConfig) {
   364  	var gen, pien, aien, wen, pen, aen, pon uint8
   365  
   366  	if cfg.GEN {
   367  		gen = 1
   368  	}
   369  	if cfg.PIEN {
   370  		pien = 1
   371  	}
   372  	if cfg.AIEN {
   373  		aien = 1
   374  	}
   375  	if cfg.WEN {
   376  		wen = 1
   377  	}
   378  	if cfg.PEN {
   379  		pen = 1
   380  	}
   381  	if cfg.AEN {
   382  		aen = 1
   383  	}
   384  	if cfg.PON {
   385  		pon = 1
   386  	}
   387  
   388  	data := []byte{gen<<6 | pien<<5 | aien<<4 | wen<<3 | pen<<2 | aen<<1 | pon}
   389  	legacy.WriteRegister(d.bus, d.Address, APDS9960_ENABLE_REG, data)
   390  
   391  	if cfg.PON {
   392  		time.Sleep(time.Millisecond * 10)
   393  	}
   394  }
   395  
   396  func (d *Device) readStatus(param string) bool {
   397  	data := []byte{0}
   398  	legacy.ReadRegister(d.bus, d.Address, APDS9960_STATUS_REG, data)
   399  
   400  	switch param {
   401  	case "CPSAT":
   402  		return data[0]>>7&0x01 == 1
   403  	case "PGSAT":
   404  		return data[0]>>6&0x01 == 1
   405  	case "PINT":
   406  		return data[0]>>5&0x01 == 1
   407  	case "AINT":
   408  		return data[0]>>4&0x01 == 1
   409  	case "PVALID":
   410  		return data[0]>>1&0x01 == 1
   411  	case "AVALID":
   412  		return data[0]&0x01 == 1
   413  	default:
   414  		return false
   415  	}
   416  }
   417  
   418  func getPulseLength(l uint8) uint8 {
   419  	switch l {
   420  	case 4:
   421  		return 0
   422  	case 8:
   423  		return 1
   424  	case 16:
   425  		return 2
   426  	case 32:
   427  		return 3
   428  	default:
   429  		return 0
   430  	}
   431  }
   432  
   433  func getPulseCount(c uint8) uint8 {
   434  	if c < 1 && c > 64 {
   435  		return 0
   436  	}
   437  	return c - 1
   438  }
   439  
   440  func getProximityGain(g uint8) uint8 {
   441  	switch g {
   442  	case 1:
   443  		return 0
   444  	case 2:
   445  		return 1
   446  	case 4:
   447  		return 2
   448  	case 8:
   449  		return 3
   450  	default:
   451  		return 0
   452  	}
   453  }
   454  
   455  func getALSGain(g uint8) uint8 {
   456  	switch g {
   457  	case 1:
   458  		return 0
   459  	case 4:
   460  		return 1
   461  	case 16:
   462  		return 2
   463  	case 64:
   464  		return 3
   465  	default:
   466  		return 0
   467  	}
   468  }