Soft Sensing Examples - Adafruit Circuit Playground Bluefruit

The following short Python programs demonstrate analog input features of the Adafruit Circuit Playground Bluefruit (CPB) board. These assume the board has additional external sensor circuits attached.

Each can be run by copying the program into code.py on the CIRCUITPY drive offered by the board. The text can be pasted directly from this page, or each file can be downloaded from the CircuitPython sample code folder on this site.

Related Pages

Essential Documentation

Soft Pressure Sensor

The following sketch assumes that an external jumper and sensor are attached to the board. The A3 pad is programmed to be a digital input with a ‘pull-up resistor’. This internally connects the pin to the 3.3V supply voltage through a resistor and allows it to emit a small current. This current flows out S+ and back through S-, going through the resistive sensor. This current creates a voltage across the sensor which can then be sensed as a voltage on analog input A2.

../_images/soft-sensor-circuit.jpg

The resistive sensor is connected between A2 and GND, represented by the yellow S+ and green S- lines. The excitation current is supplied from A3 using a jumper wire.

Direct download: cpb_soft_analog.py.

The sketch prints values on the console in a list format which can be plotted in real time using the Plotter pane in the mu editor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# cpb_soft_analog.py

# Demonstrate measurement of an analog soft sensor input.  This is intended to attach
# to an external soft touch sensor hand-fabricated using conductive fabric and
# Velostat.  The electrical resistance of the sensor varies with pressure.

# This example uses three pads on the board: A3, A2, and GND.  The A3 pad is
# used as a electrical current source by placing it in a digital-input mode with
# the internal pullup resistor enabled.  The pad then emits a small sensing
# voltage which we use to probe the resistive sensor.  As mechanical pressure is
# applied, the sensor resistance drops along with the voltage across the sensor.
# This voltage is measured by the A2 pad, used in the analog-input mode.  Please
# note that the sensor voltage follows an inverse logic: when not pressed, the
# input value will be midrange, and when pressed, it will drop (possibly to
# zero).

# The wiring for this is as follows:
#  1. One half of sensor connected to GND (either half).
#  2. Other half of sensor connected to A2.
#  3. Jumper wire connects A3 to A2.

# Note on A3: the corresponding digital input is D10.  Empirically the internal
# pullup resistor appears to be about 13.5K connected to 3.3V, e.g. it will
# drive a 10K external resistance to 1.4 Volts.

# ----------------------------------------------------------------
# Import the standard Python time functions.
import time

# Import the low-level hardware libraries.
import board
import digitalio
import analogio

# ----------------------------------------------------------------
# Initialize hardware.

# Configure the digital input pin used for its pullup resistor bias voltage.
bias = digitalio.DigitalInOut(board.D10)
bias.switch_to_input(pull=digitalio.Pull.UP)

# Configure the analog input pin used to measure the sensor voltage.
sensor = analogio.AnalogIn(board.A2)
scaling = sensor.reference_voltage / (2**16)

# ----------------------------------------------------------------
# Begin the main processing loop.
while True:

    # Read the integer sensor value and scale it to a value in Volts.
    volts = sensor.value * scaling
    
    # Every handmade sensor will have different properties, so the
    # interpretation of the voltage value will usually need to be individually
    # calibrated.  One way to accomplish this is by measuring the voltage
    # response under different conditions, then applying additional scaling and
    # offset to produce a normalized signal (possibly inverted).  For example,
    # if the observed voltage ranges between 1.4 and 0.1 Volts, the input signal
    # could be normalized to a [0,1] range as follows:
    pressure = (1.4 - volts) / (1.4 - 0.1)

    # Print the voltage reading and normalized value on the console.
    print(f"({volts}, {pressure})")
    
    # Limit the rate of the main loop.
    time.sleep(0.1)

Soft Switch, Soft Pressure Sensor, Light, and Sound

This example extends the sketch to include a binary (on/off) soft switch input on A4 and adds an LED display of the results.

Direct download: cpb_soft_sensing.py.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# cpb_soft_sensing.py

# Demonstrate measurement of several soft sensor inputs in both analog and digital modes.
# The values are indicated using the NeoPixel LEDs and speaker beeps.

# This example uses the A3 pad as a pullup for a resistive sensor connected
# between A2 and GND.  The wiring for this is as follows:
#
#  1. One half of resistive pressure sensor connected to A2 (either half).
#  2. Other half of sensor connected to GND.
#  3. Jumper wire connects A3 to A2.

# The A4 pad is used as a binary digital input with an internal pullup.  The
# wiring for this is follows:
#
#  1. One half of a soft switch sensor is connected to A4 (either half).
#  2. Other half of switch connected to GND.

# ----------------------------------------------------------------
# Import any needed standard Python modules.
import time

# Import the board-specific input/output library.
from adafruit_circuitplayground import cp

# Import the low-level hardware libraries.
import board
import digitalio
import analogio

# ----------------------------------------------------------------
# Initialize hardware.

# Configure the digital input pin used for its pullup resistor bias voltage.
bias = digitalio.DigitalInOut(board.D10)        # pad A3
bias.switch_to_input(pull=digitalio.Pull.UP)

# Configure the analog input pin used to measure the sensor voltage.
sensor = analogio.AnalogIn(board.A2)
scaling = sensor.reference_voltage / (2**16)

# Configure the digital input pin used as a soft switch.
switch = digitalio.DigitalInOut(board.D3)       # pad SCL A4
switch.switch_to_input(pull=digitalio.Pull.UP)

# Configure the NeoPixel LED array for bulk update using show().
cp.pixels.auto_write = False

# Turn down the NeoPixel brightness, otherwise it is somewhat blinding.
cp.pixels.brightness = 0.5

# ----------------------------------------------------------------
# Define functions used by the main loop.

# Use the CPB NeoPixel LEDS as a dial gauge.  A normalized value between zero
# and one is shown as a spot of light like a dial needle.  The USB connector is
# considered to be the top; the LED to its right is zero, with values increasing
# clockwise around to the LED to the left of the USB.  The spot is anti-aliased
# to increase precision, i.e. it cross-fades between LEDs as the value changes.

def update_LED_dial(value):
    # Clamp the input to the range limits.
    clamped = min(max(value, 0.0), 1.0)

    # Convert the normalized value to an indicator angle in degrees with respect to the top.
    indicator = (clamped * 300) + 30

    # Project the angle onto the NeoPixels. The list assumes the Neopixels are
    # uniformly spaced at 30 degree intervals, with the bottom position absent
    # (e.g. at the battery connector).
    for i, led_angle in enumerate((330, 300, 270, 240, 210, 150, 120, 90, 60, 30)):
        diff = abs(indicator - led_angle)
        if diff < 30:
            c = min(max(255 - int(10*diff), 0), 255)
            cp.pixels[i] = (c, c, c) # shade of gray
        else:
            cp.pixels[i] = (0,0,0)   # no light

    # Send new data to the physical LED chain.
    cp.pixels.show()

# ----------------------------------------------------------------
# Initialize global variables for the main loop.

# Integer time stamp for the next console output.
next_print_time = time.monotonic_ns()

# Previous touch value, used to detect changes.
last_touch = 0

# ----------------------------------------------------------------
# Begin the main processing loop.
while True:

    # Read the current integer clock.
    now = time.monotonic_ns()

    # Read the integer sensor value and scale it to a value in Volts.
    volts = sensor.value * scaling

    # Normalize the soft sensor reading.  Typically you'll need to adjust these values to your device.
    pressure = (1.4 - volts) / (1.4 - 0.1)

    # Update the LED dial gauge display.
    update_LED_dial(pressure)

    # Read and invert the soft switch input.
    touched = 0 if switch.value else 1

    # Emit a brief sound when the soft switch is pressed or released
    if touched != last_touch:
        last_touch = touched

        if touched == 1:
            cp.play_tone(392, 0.1)
        else:
            cp.play_tone(262, 0.1)

    # Poll the time stamp to decide whether to emit console output.
    if now >= next_print_time:

        # Advance the time stamp to the next event time.
        next_print_time += 100000000 # 0.1 seconds in nanosecond units

        # Periodically print the readings on the console.
        print(f"({volts}, {pressure}, {touched})")