From 68f531c7511a55da8ab816a2427a095115e0938c Mon Sep 17 00:00:00 2001 From: Matt Aimonetti Date: Mon, 18 Dec 2017 09:14:21 -0800 Subject: [PATCH] cap1188 experimental device (#192) This driver was tested against a adafruit version of the cap1188 https://learn.adafruit.com/adafruit-cap1188-breakout/overview and the pimoroni drumhat: https://shop.pimoroni.com/collections/raspberry-pi/products/drum-hat (which has its LED reversed and uses a different i2c address). --- AUTHORS | 1 + CONTRIBUTORS | 1 + experimental/devices/cap1188/cap1188.go | 522 ++++++++++++++++++ .../devices/cap1188/cap1188_options.go | 144 +++++ experimental/devices/cap1188/cap1188_test.go | 348 ++++++++++++ 5 files changed, 1016 insertions(+) create mode 100644 experimental/devices/cap1188/cap1188.go create mode 100644 experimental/devices/cap1188/cap1188_options.go create mode 100644 experimental/devices/cap1188/cap1188_test.go diff --git a/AUTHORS b/AUTHORS index e3b7385..231a835 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,6 +4,7 @@ # some cases, their employer may be the copyright holder. To see the full list # of contributors, see the revision history in source control. Google Inc. +Matt Aimonetti Max Ekman Stephan Sperber Thorsten von Eicken diff --git a/CONTRIBUTORS b/CONTRIBUTORS index fc9f5d2..de8f8c3 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -28,6 +28,7 @@ Hidetoshi Shimokawa Marc-Antoine Ruel +Matt Aimonetti Max Ekman Matias Insaurralde Stephan Sperber diff --git a/experimental/devices/cap1188/cap1188.go b/experimental/devices/cap1188/cap1188.go new file mode 100644 index 0000000..107fecf --- /dev/null +++ b/experimental/devices/cap1188/cap1188.go @@ -0,0 +1,522 @@ +// Copyright 2017 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// Package cap1188 controls a Microchip cap1188 device over I²C. +// The device is a 8 Channel Capacitive Touch Sensor with 8 LED Drivers +// +// Datasheet +// +// The official data sheet can be found here: +// +// http://ww1.microchip.com/downloads/en/DeviceDoc/CAP1188.pdf +// +package cap1188 + +import ( + "encoding/binary" + "errors" + "fmt" + "log" + "time" + + "periph.io/x/periph/conn" + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/i2c" + "periph.io/x/periph/conn/mmr" + "periph.io/x/periph/conn/spi" + "periph.io/x/periph/devices" +) + +// TouchStatus is the status of an input sensor +type TouchStatus int8 + +func (t TouchStatus) String() string { + switch t { + case OffStatus: + return strOffStatus + case PressedStatus: + return strPressedStatus + case HeldStatus: + return strHeldStatus + case ReleasedStatus: + return strReleasedStatus + default: + return "Unknown" + } +} + +const ( + // OffStatus indicates that the input sensor isn't being activated + OffStatus TouchStatus = iota + // PressedStatus indicates that the input sensor is currently pressed + PressedStatus + // HeldStatus indicates that the input sensor was pressed and is still held pressed + HeldStatus + // ReleasedStatus indicates that the input sensor was pressed and is being released + ReleasedStatus +) + +const ( + nbrOfLEDs = 8 + strOffStatus = "Off" + strPressedStatus = "Pressed" + strHeldStatus = "Held" + strReleasedStatus = "Released" +) + +const ( + // reg_LEDLinking - The Sensor Input LED Linking register controls whether a + // capacitive touch sensor input is linked to an LED output. If the + // corresponding bit is set, then the appropriate LED output will change + // states defined by the LED Behavior controls in response to the capacitive + // touch sensor input. + reg_LEDLinking = 0x72 + // reg_LEDOutputControl - The LED Output Control Register controls the output + // state of the LED pins that are not linked to sensor inputs. + reg_LEDOutputControl = 0x74 +) + +// Dev is a handle to a cap1188. +type Dev struct { + Opts + d conn.Conn + regWrapper mmr.Dev8 + isSPI bool + inputStatuses []TouchStatus + resetAt time.Time +} + +func (d *Dev) String() string { + return fmt.Sprintf("cap1188{%s}", d.regWrapper.Conn) +} + +// Halt is a noop for the cap1188. +func (d *Dev) Halt() error { + return nil +} + +// InputStatus reads and returns the status of the 8 inputs as an array where +// each entry indicates a touch event or not. +func (d *Dev) InputStatus() ([]TouchStatus, error) { + // first check that we are ready + now := time.Now() + readyAt := d.resetAt.Add(200 * time.Millisecond) + if now.Before(readyAt) { + time.Sleep(readyAt.Sub(now)) + } + // read inputs + status, err := d.regWrapper.ReadUint8(0x3) + if err != nil { + return d.inputStatuses, wrap(fmt.Errorf("failed to read the input values - %s", err)) + } + + // read deltas (in two's complement, capped at -128 to 127) + deltasB := [nbrOfLEDs]byte{} + if err = d.regWrapper.ReadStruct(0x10, &deltasB); err != nil { + return d.inputStatuses, wrap(fmt.Errorf("failed to read the delta values - %s", err)) + } + deltas := [nbrOfLEDs]int{} + for i, b := range deltasB { + deltas[i] = int(int8(b)) + } + + // read threshold + thresholds := [nbrOfLEDs]byte{} + if err = d.regWrapper.ReadStruct(0x30, &thresholds); err != nil { + return d.inputStatuses, wrap(fmt.Errorf("failed to read the threshold values - %s", err)) + } + + // convert the data into a sensor state + var touched bool + for i := uint8(0); i < uint8(len(d.inputStatuses)); i++ { + // check if the bit is set. + touched = (status>>(7-i))&1 == 1 + + // TODO(mattetti): check if the event is passed the threshold: + // deltas[i] > int(thresholds[i]) + + if touched { + if d.inputStatuses[i] == PressedStatus { + if d.RetriggerOnHold { + d.inputStatuses[i] = HeldStatus + } + continue + } + d.inputStatuses[i] = PressedStatus + } else { + d.inputStatuses[i] = OffStatus + } + } + + return d.inputStatuses, nil +} + +// LinkLEDs links the behavior of the LEDs to the touch sensors. +// Doing so, disabled the option for the host to set specific LEDs on/off. +func (d *Dev) LinkLEDs(on bool) error { + if on { + if err := d.regWrapper.WriteUint8(reg_LEDLinking, 0xff); err != nil { + return wrap(fmt.Errorf("failed to link LEDs - %s", err)) + } + } else { + if err := d.regWrapper.WriteUint8(reg_LEDLinking, 0x00); err != nil { + return wrap(fmt.Errorf("failed to unlink LEDs - %s", err)) + } + } + d.LinkedLEDs = on + return nil +} + +// AllLEDs turns all the LEDs on or off. +// +// This is quite more efficient than looping through each led and turn them on/off. +func (d *Dev) AllLEDs(on bool) error { + if d.LinkedLEDs { + return wrap(fmt.Errorf("can't manually set LEDs when they are linked to sensors")) + } + if on { + return d.regWrapper.WriteUint8(reg_LEDOutputControl, 0xff) + } + + return d.regWrapper.WriteUint8(reg_LEDOutputControl, 0x00) +} + +// SetLED sets the state of a LED as on or off +// Only works if the LEDs are not linked to the sensors +func (d *Dev) SetLED(idx int, state bool) error { + if d.LinkedLEDs { + return wrap(fmt.Errorf("can't manually set LEDs when they are linked to sensors")) + } + if idx > 7 || idx < 0 { + return wrap(fmt.Errorf("invalid led idx %d", idx)) + } + if d.Debug { + log.Printf("Set LED state %d - %t\n", idx, state) + } + if state { + return d.setBit(reg_LEDOutputControl, idx) + } + return d.clearBit(reg_LEDOutputControl, idx) +} + +// Reset issues a soft reset to the device using the reset pin +// if available. +func (d *Dev) Reset() (err error) { + d.ClearInterrupt() + if d != nil && d.ResetPin != nil { + if d.Debug { + log.Println("cap1188: Resetting the device using the reset pin") + } + if err = d.ResetPin.Out(gpio.Low); err != nil { + return err + } + time.Sleep(1 * time.Microsecond) + if err = d.ResetPin.Out(gpio.High); err != nil { + return err + } + time.Sleep(10 * time.Millisecond) + if err = d.ResetPin.Out(gpio.Low); err != nil { + return err + } + } + // track the reset time since the device won't be ready for up to 15ms + // and won't be ready for first conversion for up to 200ms + d.resetAt = time.Now() + + return nil +} + +// ClearInterrupt resets the interrupt flag +func (d *Dev) ClearInterrupt() error { + // clear the main control bit + return d.clearBit(0x0, 0) +} + +// NewI2C returns a new device that communicates over I²C to cap1188. +func NewI2C(b i2c.Bus, opts *Opts) (*Dev, error) { + addr := uint16(0x28) // default address + if opts != nil { + switch opts.Address { + case 0x28, 0x29, 0x2a, 0x2b, 0x2c: + addr = opts.Address + case 0x00: + // do not do anything + default: + return nil, wrap(errors.New("given address not supported by device")) + } + } + d := &Dev{d: &i2c.Dev{Bus: b, Addr: addr}, isSPI: false} + if d.Debug { + log.Printf("cap1188: Connecting via I2C address: %#X\n", addr) + } + d.inputStatuses = make([]TouchStatus, nbrOfLEDs) + if err := d.makeDev(opts); err != nil { + return nil, err + } + // time to communications is 15ms + now := time.Now() + readyAt := d.resetAt.Add(15 * time.Millisecond) + if now.Before(readyAt) { + time.Sleep(readyAt.Sub(now)) + } + return d, nil +} + +// NewSPI returns an object that communicates over SPI to cap1188 environmental +// sensor. +func NewSPI(p spi.Port, opts *Opts) (*Dev, error) { + return nil, fmt.Errorf("not implemented") +} + +func (d *Dev) makeDev(opts *Opts) error { + // Use default options if none are passed. + if opts == nil { + opts = DefaultOpts() + } + d.Opts = *opts + d.regWrapper = mmr.Dev8{Conn: d.d, Order: binary.LittleEndian} + + var productID byte + var err error + // Read the product id to confirm it matches our expectations. + if productID, err = d.regWrapper.ReadUint8(0xFD); err != nil { + return fmt.Errorf("failed to read product id - %s", err) + } + if productID != 0x50 { + return fmt.Errorf("cap1188: unexpected chip id %x; is this a cap1188?", productID) + } + // manufacturer ID on 0xFE, should be 0x5D + // revision ID on 0xFF, should be 0x83 + + // reset the device + if err = d.Reset(); err != nil { + return fmt.Errorf("failed to reset the device - %s", err) + } + + var recalFlag byte + if opts.EnableRecalibration { + recalFlag = 1 + } + var intOnRel byte + if !opts.InterruptOnRelease { + intOnRel = 1 // 0 = trigger on release + } + + // enable all inputs + if err = d.regWrapper.WriteUint8(0x21, 0xff); err != nil { + return wrap(fmt.Errorf("failed to enable all inputs - %s", err)) + } + // enable interrupts + if err = d.regWrapper.WriteUint8(0x27, 0xff); err != nil { + return wrap(fmt.Errorf("failed to enable interrupts - %s", err)) + } + // enable/disable repeats + // TODO(mattetti): make it an option + if err = d.regWrapper.WriteUint8(0x28, 0xff); err != nil { + return fmt.Errorf("failed to disable repeats - %s", err) + } + // enable multitouch + multitouchConfig := ( + // Enables the multiple button blocking circuitry. + // „ ‘0’ - The multiple touch circuitry is disabled. The device will not + // block multiple touches. + // „ ‘1’ (default) - The multiple touch circuitry is enabled. The device + // will flag the number of touches equal to programmed multiple touch + // threshold and block all others. It will remember which sensor inputs + // are valid and block all others until that sensor pad has been + // released. Once a sensor pad has been released, the N detected touches + // (determined via the cycle order of CS1 - CS8) will be flagged and all + // others blocked. + byte(0)<<7 | + byte(0)<<6 | byte(0)<<5 | byte(0)<<4 | + // Determines the number of simultaneous touches on all sensor pads + // before a Multiple Touch Event is detected and sensor inputs are blocked. + // set to 2 + byte(0)<<3 | byte(1)<<2 | + byte(0)<<1 | byte(0)<<0) + if err = d.regWrapper.WriteUint8(0x2a, multitouchConfig); err != nil { + return wrap(fmt.Errorf("failed to enable multitouch - %s", err)) + } + // Averaging and Sampling Config + samplingConfig := (byte(0)<<7 | + // number of samples taken per measurement + // TODO(mattetti): use opts.SamplesPerMeasurement + byte(0)<<6 | + byte(0)<<5 | + byte(0)<<4 | + // sample time + // TODO(mattetti): use opts.SamplingTime + byte(1)<<3 | + byte(0)<<2 | + // overall cycle time + // TODO(mattetti): use opts.CycleTime + byte(0)<<1 | + byte(0)<<0) + if d.Debug { + log.Printf("cap1188: Sampling config mask: %08b\n", samplingConfig) + } + if err = d.regWrapper.WriteUint8(0x24, samplingConfig); err != nil { + return fmt.Errorf("failed to enable multitouch - %s", err) + } + + // customize sensitivity (TODO) + sensitivity := (byte(0)<<7 | + // Controls the sensitivity of a touch detection. The sensitivity settings act + // to scale the relative delta count value higher or lower based on the system parameters. A setting of + // 000b is the most sensitive while a setting of 111b is the least sensitive. At the more sensitive settings, + // touches are detected for a smaller delta capacitance corresponding to a “lighter” touch. These settings + // are more sensitive to noise, however, and a noisy environment may flag more false touches with higher + // sensitivity levels. + // Set to 4x + // TODO(mattetti): make that configurable. + byte(1)<<6 | byte(0)<<5 | byte(1)<<4 | + byte(0)<<3 | byte(0)<<2 | byte(0)<<1 | byte(0)<<0) + if d.Debug { + log.Printf("cap1188: Sensitivity mask: %08b\n", sensitivity) + } + if err = d.regWrapper.WriteUint8(0x1F, sensitivity); err != nil { + return fmt.Errorf("failed to set sensitivity - %s", err) + } + + if opts.LinkedLEDs { + if err = d.LinkLEDs(true); err != nil { + return err + } + } + + if opts.RetriggerOnHold { + if err = d.regWrapper.WriteUint8(0x28, 0xff); err != nil { + return fmt.Errorf("failed to set retrigger on hold - %s", err) + } + } else { + if err = d.regWrapper.WriteUint8(0x28, 0x00); err != nil { + return fmt.Errorf("failed to turn off retrigger on hold - %s", err) + } + } + + // page 47 - configuration registers + config := ( + // Timeout: Enables the timeout and idle functionality of the SMBus protocol. + // default 0: The SMBus timeout and idle functionality are disabled. The + // SMBus interface will not time out if the clock line is held low. + // Likewise, it will not reset if both data and clock lines are held + // high for longer than 200us + byte(0)<<7 | + // Configures the operation of the WAKE pin. + // default 0: The WAKE pin is not asserted when a touch is detected while the + // device is in Standby. It will still be used to wake the device from + // Deep Sleep when driven high. + byte(0)<<6 | + // digital noise threshold + // Determines whether the digital noise threshold is used by the device. + // default 1: The noise threshold is disabled. Any delta count that + // is less than the touch threshold is used for the automatic + // re-calibration routine. + byte(1)<<5 | + // analog noise filter + // default 0: Determines whether the analog noise filter is enabled. Setting this + // bit disables the feature. + byte(1)<<4 | + // maximum duration recalibration + // Determines whether the maximum duration recalibration is enabled. + // + // if 0, the maximum duration recalibration functionality is + // disabled. A touch may be held indefinitely and no re-calibration + // will be performed on any sensor input. + // + // if 1, The maximum duration recalibration functionality is + // enabled. If a touch is held for longer than the + // opts.MaxTouchDuration, then the re-calibration routine will be + // restarted + recalFlag<<3 | + byte(0)<<2 | + byte(0)<<1 | + byte(0)<<0) + if d.Debug { + log.Printf("cap1188: Config mask: %08b\n", config) + } + if err = d.regWrapper.WriteUint8(0x20, config); err != nil { + return fmt.Errorf("failed to set the device configuration - %s", err) + } + + config2 := ( + // Linked LED Transition controls + // default 0: The Linked LED Transition controls set the min duty + // cycle equal to the max duty cycle + byte(0)<<7 | + // Determines the ALERT# pin polarity and behavior. + // default 1: the ALERT# pin is active low and open drain. + byte(1)<<6 | + // Determines whether the device will reduce power consumption + // while waiting between conversion time completion and the end of + // the polling cycle. + // default 0: The device will always power down as much as possible + // during the time between the end of the last conversion and the + // end of the polling cycle. + byte(1)<<5 | + // Determines whether the LED Mirror Control register bits are + // linked to the LED Polarity bits. Setting this bit blocks the + // normal behavior which is to automatically set and clear the LED + // Mirror Control bits when the LED Polarity bits are set or + // cleared. + // default 0: When the LED Polarity controls are set, the + // corresponding LED Mirror control is automatically set. Likewise, + // when the LED Polarity controls are cleared, the corresponding + // LED Mirror control is also cleared. + byte(0)<<4 | + // Determines whether the Noise Status bits will show RF Noise as + // the only input source. + // default 0: The Noise Status registers will show both RF noise and + // low frequency EMI noise if either is detected on a capacitive + // touch sensor input. + byte(0)<<3 | + // Determines whether the RF noise filter is enabled. + // default 0: If RF noise is detected by the analog block, the + // delta count on the corresponding channel is set to 0. Note that + // this does not require that Noise Status bits be set. + byte(0)<<2 | + byte(0)<<1 | + // Controls the interrupt behavior when a release is detected on a button. + // when 0: An interrupt is generated when a press is detected and + // again when a release is detected and at the repeat rate (if enabled) + // when 1: An interrupt is generated when a press is detected and + // at the repeat rate but not when a release is detected. + intOnRel<<0) + if d.Debug { + log.Printf("cap1188: Config2 mask: %08b\n", config2) + } + if err = d.regWrapper.WriteUint8(0x44, config2); err != nil { + return fmt.Errorf("failed to set the device configuration 2 - %s", err) + } + + return nil +} + +// setBit sets a specific bit on a register +// TODO(mattetti): avoid reading before writing, keep states in memory +func (d *Dev) setBit(regID uint8, idx int) error { + v, err := d.regWrapper.ReadUint8(regID) + if err != nil { + return err + } + v |= (1 << uint8(idx)) + return d.regWrapper.WriteUint8(regID, v) +} + +// clearBit clears a specific bit on a register +// TODO(mattetti): avoid reading before writing, keep states in memory +func (d *Dev) clearBit(regID uint8, idx int) error { + v, err := d.regWrapper.ReadUint8(regID) + if err != nil { + return err + } + v &= ^(1 << uint8(idx)) + return d.regWrapper.WriteUint8(regID, v) +} + +func wrap(err error) error { + return fmt.Errorf("cap1188: %v", err) +} + +var _ devices.Device = &Dev{} diff --git a/experimental/devices/cap1188/cap1188_options.go b/experimental/devices/cap1188/cap1188_options.go new file mode 100644 index 0000000..9bc329e --- /dev/null +++ b/experimental/devices/cap1188/cap1188_options.go @@ -0,0 +1,144 @@ +// Copyright 2017 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package cap1188 + +import "periph.io/x/periph/conn/gpio" + +// SamplingTime determines the time to take a single sample +type SamplingTime uint8 + +// Possible sampling time values (written as 2 bits) +const ( + S320us SamplingTime = 0 + S640us SamplingTime = 1 + // S1_28ms represents 1.28ms sampling time, which is the default. + S1_28ms SamplingTime = 2 // default + S2_56ms SamplingTime = 3 +) + +// AvgSampling set the number of samples per measurement that get +// averaged +type AvgSampling uint8 + +// possible average sampling values. (written as 3 bits) +const ( + // Avg1 means that 1 sample is taken per measurement + Avg1 AvgSampling = iota // 0 + Avg2 // 1 + Avg4 // 2 + Avg8 // 3 default + Avg16 // 4 + Avg32 // 5 + Avg64 // 6 + Avg128 // 7 +) + +// CycleTime determines the overall cycle time for all measured channels during +// normal operation. +type CycleTime uint8 + +// possible cycle time values. (written as 2 bits) +const ( + C35ms CycleTime = iota // 0 + C70ms // default + C105ms + C140ms +) + +// MaxDur is the maximum duration of a touch event before it triggers a +// recalibration. +type MaxDur uint8 + +// possible touch duration values. (written as 4 bits) +const ( + MaxDur560ms MaxDur = iota + MaxDur840ms + MaxDur1120ms + MaxDur1400ms + MaxDur1680ms + MaxDur2240ms + MaxDur2800ms + MaxDur3360ms + MaxDur3920ms + MaxDur44800ms + MaxDur5600ms // default + MaxDur6720ms + MaxDur7840ms + MaxDur8906ms + MaxDur10080ms + MaxDur11200ms +) + +// Opts is optional options to pass to the constructor. +// +// Address is only used on creation of an I²C-device. Its default value is 0x28. +// It can be set to other values (0x29, 0x2a, 0x2b, 0x2c) depending on the HW +// configuration of the ADDR_COMM pin. This has no effect with NewSPI() +type Opts struct { + // Address is the I2C slave address to use + Address uint16 + + // Debug turns on extra logging capabilities + Debug bool + // LinkedLEDs indicates if the LEDs should be activated automatically + // when their sensors detect a touch event. + LinkedLEDs bool + // MaxTouchDuration sets the touch duration threshold. It is possible that a + // “stuck button” occurs when something is placed on a button which causes a + // touch to be detected for a long period. By setting this value, + // a recalibration can be forced when a touch is held on a button for longer + // than the duration specified. + MaxTouchDuration MaxDur + // EnableRecalibration is used to force the recalibration if a touch event lasts + // longer than MaxTouchDuration. + EnableRecalibration bool + + // AlertPin is the pin receiving the interrupt when a touch event is detected + // and optionally if a release event is detected. + AlertPin gpio.PinIn + // ResetPin is the pin used to reset the device. + ResetPin gpio.PinOut + + // InterruptOnRelease indicates if the device should also trigger an + // interrupt on the AlertPin when a release event is detected. + InterruptOnRelease bool + + // RetriggerOnHold forces a retrigger of the interrupt when a sensor is pressed + // for longer than MaxTouchDuration + RetriggerOnHold bool + + // Averaging and Sampling Configuration Register + + // SamplesPerMeasurement is the number of samples taken per measurement. All + // samples are taken consecutively on the same channel before the next + // channel is sampled and the result is averaged over the number of samples + // measured before updating the measured results. + // Available options: 1, 2, 4, 8 (default), 16, 32, 64, 128 + SamplesPerMeasurement AvgSampling + + // SamplingTime Determines the time to take a single sample as shown + SamplingTime SamplingTime + + // CycleTime determines the overall cycle time for all measured channels + // during normal operation. All measured channels are sampled at the + // beginning of the cycle time. If additional time is remaining, then the + // device is placed into a lower power state for the remaining duration of + // the cycle. + CycleTime CycleTime +} + +// DefaultOpts returns a pointer to a new Opts with the default option values. +func DefaultOpts() *Opts { + return &Opts{ + LinkedLEDs: true, + MaxTouchDuration: MaxDur5600ms, + RetriggerOnHold: false, + EnableRecalibration: false, + InterruptOnRelease: false, + SamplesPerMeasurement: Avg1, + SamplingTime: S1_28ms, + CycleTime: C35ms, + } +} diff --git a/experimental/devices/cap1188/cap1188_test.go b/experimental/devices/cap1188/cap1188_test.go new file mode 100644 index 0000000..af31ab0 --- /dev/null +++ b/experimental/devices/cap1188/cap1188_test.go @@ -0,0 +1,348 @@ +// Copyright 2017 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +package cap1188_test + +import ( + "fmt" + "log" + "reflect" + "testing" + "time" + + "periph.io/x/periph/conn/gpio" + "periph.io/x/periph/conn/gpio/gpioreg" + "periph.io/x/periph/conn/i2c" + "periph.io/x/periph/conn/i2c/i2creg" + "periph.io/x/periph/conn/i2c/i2ctest" + "periph.io/x/periph/experimental/devices/cap1188" +) + +func Example() { + // Open the I²C bus to which the cap1188 is connected. + i2cBus, err := i2creg.Open("") + if err != nil { + log.Fatal(err) + } + defer i2cBus.Close() + + // We will configure the cap1188 by setting some options, we can start by the defaults. + opts := cap1188.DefaultOpts() + + // We need to set an alert ping that will let us know when a touch event + // occurs. The alert pin is the pin connected to the IRQ/interrupt pin. + alertPin := gpioreg.ByName("GPIO25") + if alertPin == nil { + log.Fatal("invalid alert GPIO pin number") + } + // We set the alert pin to monitor for interrupts + if err := alertPin.In(gpio.PullUp, gpio.BothEdges); err != nil { + log.Fatalf("Can't monitor the alert pin") + } + + // Optionally but highly recommended, we can also set a reset pin to + // start/leave things in a clean state. + resetPin := gpioreg.ByName("GPIO21") + if resetPin == nil { + log.Fatal("invalid reset GPIO pin number") + } + opts.AlertPin = alertPin + opts.ResetPin = resetPin + + // open the device so we can detect touch events + dev, err := cap1188.NewI2C(i2cBus, opts) + if err != nil { + log.Fatalf("couldn't open cap1188 - %s", err) + } + time.Sleep(200 * time.Millisecond) + + fmt.Println("Monitoring for touch events") + maxTouches := 42 // stop the program after 42 touches + for maxTouches > 0 { + if alertPin.WaitForEdge(-1) { + maxTouches-- + statuses, err := dev.InputStatus() + if err != nil { + fmt.Printf("Error reading inputs: %s\n", err) + continue + } + // print the status of each sensor + for i, st := range statuses { + fmt.Printf("#%d: %s\t", i, st) + } + fmt.Println() + // we need to clear the interrupt so it can be triggered again + if err := dev.ClearInterrupt(); err != nil { + fmt.Println(err, "while clearing the interrupt") + } + } + } + + fmt.Print("\n") +} + +func TestNewI2C(t *testing.T) { + bus := i2ctest.Playback{ + Ops: []i2ctest.IO{ + // chip ID + {Addr: 40, W: []byte{0xfd}, R: []byte{0x50}}, + // clear interrupt + {Addr: 40, W: []byte{0x0}, R: []byte{0x0}}, + {Addr: 40, W: []byte{0x0, 0x0}, R: nil}, + // enable all inputs + {Addr: 40, W: []byte{0x21, 0xff}, R: nil}, + // enable interrupts + {Addr: 40, W: []byte{0x27, 0xff}, R: nil}, + // enable/disable repeats + {Addr: 40, W: []byte{0x28, 0xff}, R: nil}, + // multitouch + {Addr: 40, W: []byte{0x2a, 0x4}, R: nil}, + // sampling + {Addr: 40, W: []byte{0x24, 0x8}, R: nil}, + // sensitivity + {Addr: 40, W: []byte{0x1f, 0x50}, R: nil}, + // don't retrigger on hold + {Addr: 40, W: []byte{0x28, 0x0}, R: nil}, + // config + {Addr: 40, W: []byte{0x20, 0x30}, R: nil}, + // config 2 + {Addr: 40, W: []byte{0x44, 0x61}, R: nil}, + }, + } + d, err := cap1188.NewI2C(&bus, &cap1188.Opts{Debug: true}) + if err != nil { + t.Fatal(err) + } + if s := d.String(); s != "cap1188{playback(40)}" { + t.Fatal(s) + } + if err := d.Halt(); err != nil { + t.Fatal(err) + } + if err := bus.Close(); err != nil { + t.Fatal(err) + } +} + +func TestDev_InputStatus(t *testing.T) { + tests := []struct { + name string + bus i2c.Bus + opts *cap1188.Opts + want []cap1188.TouchStatus + }{ + {name: "all off", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + // status + {Addr: 40, W: []byte{0x3}, R: []byte{0x0}}, + // deltas + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + // thresholds + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}, + }, + {name: "all pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{0xff}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus, cap1188.PressedStatus}, + }, + {name: "first pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 7}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}, + }, + {name: "second pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 6}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}, + }, + {name: "third pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 5}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}, + }, + {name: "forth pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 4}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}, + }, + {name: "fifth pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 3}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}, + }, + {name: "sixth pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 2}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus}, + }, + {name: "seventh pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 1}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus}, + }, + {name: "eighth pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 0}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus}, + }, + {name: "3 pressed", + bus: &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{(1 << 7) ^ (1 << 4) ^ (1 << 0)}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + }, + opts: cap1188.DefaultOpts(), + want: []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.PressedStatus}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d, err := cap1188.NewI2C(tt.bus, tt.opts) + if err != nil { + t.Fatal(err) + } + got, err := d.InputStatus() + if err != nil { + t.Fatalf("Dev.InputStatus() error = %v", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Dev.InputStatus() = %v, want %v", got, tt.want) + } + }) + } + // test hold + t.Run("held touch sensors", func(t *testing.T) { + bus := &i2ctest.Playback{ + Ops: append(setupPlaybackIO(), []i2ctest.IO{ + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 7}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + // repeat call to get status (still pressed) + {Addr: 40, W: []byte{0x3}, R: []byte{1 << 7}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + // finall call + {Addr: 40, W: []byte{0x3}, R: []byte{0x0}}, + {Addr: 40, W: []byte{0x10}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + {Addr: 40, W: []byte{0x30}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, + }...), + } + // set the recorded response to have the retrigger option on + bus.Ops[10] = i2ctest.IO{Addr: 40, W: []byte{0x28, 0xff}, R: nil} + opts := cap1188.DefaultOpts() + // following option needs to be true so we can get the held status + opts.RetriggerOnHold = true + d, err := cap1188.NewI2C(bus, opts) + if err != nil { + t.Fatal(err) + } + // first check + got, err := d.InputStatus() + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, []cap1188.TouchStatus{cap1188.PressedStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}) { + t.Fatalf("expected to have the first sensor touched but instead got %v", got) + } + // 2nd check + got, err = d.InputStatus() + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, []cap1188.TouchStatus{cap1188.HeldStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus, cap1188.OffStatus}) { + t.Fatalf("expected to have the first sensor touched but instead got %v", got) + } + }) +} + +func setupPlaybackIO() []i2ctest.IO { + return []i2ctest.IO{ + // chip ID + {Addr: 40, W: []byte{0xfd}, R: []byte{0x50}}, + // clear interrupt + {Addr: 40, W: []byte{0x0}, R: []byte{0x0}}, + {Addr: 40, W: []byte{0x0, 0x0}, R: nil}, + // enable all inputs + {Addr: 40, W: []byte{0x21, 0xff}, R: nil}, + // enable interrupts + {Addr: 40, W: []byte{0x27, 0xff}, R: nil}, + // enable/disable repeats + {Addr: 40, W: []byte{0x28, 0xff}, R: nil}, + // multitouch + {Addr: 40, W: []byte{0x2a, 0x4}, R: nil}, + // sampling + {Addr: 40, W: []byte{0x24, 0x8}, R: nil}, + // sensitivity + {Addr: 40, W: []byte{0x1f, 0x50}, R: nil}, + // linked leds + {Addr: 40, W: []byte{0x72, 0xff}, R: nil}, + // don't retrigger on hold + {Addr: 40, W: []byte{0x28, 0x0}, R: nil}, + // config + {Addr: 40, W: []byte{0x20, 0x30}, R: nil}, + // config 2 + {Addr: 40, W: []byte{0x44, 0x61}, R: nil}, + } +}