gabriel stacey-chartrand

Week 09: input devices

This week I worked with five different inputs: a custom mutual capacitance proximity detector, a velostat pressure sensor, a KY-040 rotary encoder, the BNO085 IMU as an accelerometer for yaw sensing, and the BNO085 magnetometer as a magnetic rotary encoder. I didn't have a dedicated encoder IC like an AS5600 on hand, so I used the magnetometer directly with a small cube magnet.

Mutual capacitance touch / proximity sensor

I wanted to make a sensor that could detect proximity, not just touch. Mutual capacitance works by transmitting a charge on one electrode and measuring how much coupling makes it across to the receiving electrode, a nearby hand disturbs that coupling in a measurable way.

initial breadboard setup, two copper foil pads connected to Barduino (ESP32-S3) for mutual capacitance sensing

This demo was with the original simple copper pad pair, just to prove the concept worked before going further.

Since the CapacitiveSensor library only works on AVR boards, I asked Claude to write custom charge-transfer sensing logic for the ESP32. This was the first version that actually ran, values sitting around 22 at rest and spiking to about 195 on direct pad touch.

CapacitiveSensor library not available on ESP32, it's AVR-only, so I had to write the charge-transfer logic from scratch

I asked Claude to try an analogRead approach with a brief charge window to simplify things. The readings came out completely inverted, the sensor was reading TOUCH at rest and dropping when I actually touched the pad.

To try to fix the inverted readings, I added a startup baseline calibration and more averaging. The delta values stayed at 0–2 regardless of where my hand was. analogRead just wasn't working.

I stripped everything back to bare analogRead with no averaging to rule out any software artifacts. The output was completely flat at around 3750, zero response to hand proximity. The ESP32-S3 ADC is just too noisy for this.

At this point I cut an interdigitated electrode pattern with a utility knife. The comb geometry puts much more shared edge between the TX and RX pads, which dramatically increases the coupling area.

I found this fanatastic document on capactive touch sensor design: Capacitive Touch Sensor Design Guide

close-up of the original copper foil pads, TX to GPIO 12, RX to GPIO 13, 1MΩ resistor bridging both signal lines
interdigitated TX/RX copper pad, the comb pattern maximises coupling area between the two electrodes

I went back to the charge-counting approach and got real proximity data, delta 1 at 9cm, delta 2 at 3cm, delta 3 at 1cm. The signal was there but weak.

I cranked samples up to 2000 to try to average out the noise, but the response barely moved. Claude pointed out that the pad geometry itself was the bottleneck, two small squares sitting side by side don't have enough shared edge to build up a meaningful signal.

I also tested the sensor through a piece of 2mm thick PLA from a test print that was laying around and it was able to detect proximity as well as touch.

Velostat pressure sensor

I then made my own pressure sensor from copper tape and velostat, a carbon-black film that gets more conductive when you compress it. The plan was to eventually replace the tactile button for my custom pomodoro timer watch board. I wired it as a voltage divider with a 3.3K resistor and connected it to GPIO 12 on the Barduino.

soldering station set up, copper tape squares, flux, solder, and wire ready to go
velostat sensor components, two copper foil squares wired up, with a square of velostat
completed velostat sensor, velostat sandwiched between copper foil electrodes, wrapped in rubbery tape for extra travel when pressing
breadboard circuit, voltage divider wired up and connected to the Barduino for testing

The pressure sensor readout worked well in the serial monitor, from 0-4095 depending on how much force I used.

I then mapped the pressure input reading to control the brightness of the NeoPixel on the Barduino.

KY-040 rotary encoder

The KY-040 was straightforward to wire up, CLK on GPIO 13, DT on GPIO 14, SW on GPIO 15, but had a lot of bounce. I fixed that with the ESP32Encoder library. Once it was reading cleanly I mapped the position to NeoPixel brightness, then to colour temperature.

Adafruit BNO085 IMU

I wanted to explore using the BNO085 as a more interesting input. It's a 9-DOF IMU with an accelerometer, gyroscope, and magnetometer all in one. My first idea was to use the accelerometer to track yaw rotation (as a make shift rotary encoder), before moving on to the magnetometer approach.

Adafruit BNO085, 9-DOF IMU
BNO085 I2C pin documentation, STEMMA QT connector as an option
wiring a STEMMA QT connector to connect the XIAO ESP32-S3 to the BNO085
BNO085 connected to the XIAO via STEMMA QT, green LED confirms power
Adafruit Library Manager documentation for the BNO08x
installing the required libraries, Adafruit Unified Sensor, BusIO, and BNO08x
"BNO08x not found, check wiring!", the XIAO needs an explicit Wire.begin(5, 6) to use the right I2C pins
I2C scanner coming up empty, confirmed a wiring config issue, not a code problem
after adding Wire.begin(5, 6): BNO085 shows up at address 0x4A

The yaw data readout was very precise and quite stable.

BNO085 magnetometer as rotary encoder

Since I didn't have an AS5600 or similar dedicated magnetic encoder IC on hand, I tried using the BNO085 magnetometer directly. The idea was to hold a diametrically magnetized magnet above the sensor and use the raw field values to calculate the absolute angle as it rotates. I asked Claude to help me create the code to access this data. I then had Claude add a rolling average to smooth out the noise.

compile error: SH2_MAGNETIC_FIELD not declared, the correct constant is SH2_MAGNETIC_FIELD_CALIBRATED

Magnetic rotary encoder, proof of concept

I threw together a quick proof of concept from parts I had lying around to validate the BNO085 + rotating magnet approach. I also tried soldering wires directly to a NeoPixel to use as an output indicator, but held the iron on it for too long and fried it.

proof-of-concept encoder knob with cube magnet and piece of tape
BNO085 seated in the proof-of-concept enclosure with parts on hand
BNO085 in the base enclosure and rotating knob with magnet
side view of the proof-of-concept encoder
proof-of-concept in hand
attempted to solder directly to a NeoPixel, held the iron on too long and fried it

I decided to modify my week 02 braille learning device to accomodate the BNO085 and a magnet to be able to read the position of the knob. The magnet would be embedded in the knob print. I created a pocket for affixing the BNO085 with a threaded hatch for access to the board and a hole for the wires to go to my XIAO. This was only a concept that would be used for testing purposes as I continue to iterate on this design.

Fusion 360 cross-section of the encoder housing, BNO085 pocket in the base and rotating knob mechanism
slicer preview of the encoder knob, ready to print
Country roads...