Using a Proximity Sensor for Scrolling Documents
What
For Christmas, I treated myself to a Touchboard from Bare Conductive. This board features an Atmel ATMega32U4 microprocessor, 12 analog inputs, and 20 digital pins. Being part of the Arduino family, it’s compatible with the Arduino IDE, which makes it a versatile tool for various projects. The Touchboard comes with a unique conductive paint called "bare paint," which, when used with the analog inputs, allows you to create touch or proximity sensors.
First Impressions
When I first experimented with the proximity sensor, using it to adjust volume felt almost magical! I was impressed by the precision of the proximity detection. The SDK provides around 10 examples to get started, and the libraries are straightforward, allowing you to accomplish a lot with just a few lines of code. The board supports both MP3 and MIDI sound effects. MP3 files can be updated on a microSD card, while the MIDI mode offers over 100 musical instruments. Switching between MP3 and MIDI requires connecting two specific connectors on the board.
Initial Experiment
My initial idea was to create a music table, but first, I decided to experiment with scrolling functionality. Scrolling can be managed using either the keyboard or mouse. However, I found that mouse-based scrolling requires additional clicks to continue writing, so I opted for the keyboard approach.
I used the HID library to trigger keyboard and mouse controls. For this exercise, I utilized only one electrode (E0) on the Touchboard for proximity sensing. The basic logic is that when the user touches the sensor for the first time, scrolling is activated. To stop scrolling, the user needs to touch the sensor again. The area above the sensor is divided into two sections: the closer section scrolls down, while the farther section scrolls up.
The proximity sensor utilizes a filter from the "proximity example" to smooth the input and constrain values to a range of 0-50. Additionally, I implemented a function that dynamically adjusts the keyboard execution speed based on the distance of the hand from the sensor. In simpler terms, the closer your hand is to the sensor, the faster it scrolls.
Result
After a few hours of testing, I found that this scrolling method didn't enhance my productivity; in fact, it slowed me down. However, when I demonstrated it to my friend’s younger brother, he quickly turned it into a game to see who could scroll to a specific line faster!
Here is the source code:
// compiler error handling
#include "Compiler_Errors.h"
// touch includes
#include
#include
#define MPR121_ADDR 0x5C
#define MPR121_INT 4
#include
#include
// serial rate
#define baudRate 57600
#define SCROLL_ELECTRODE 0
// define LED_BUILTIN for older versions of Arduino
#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif
// mapping and filter definitions
#define LOW_DIFF 0
#define HIGH_DIFF 50
#define filterWeight 0.3f // 0.0f to 1.0f - higher value = more smoothing
float lastProx = 0;
#define SCROLL_ENABLED 1
#define SCROLL_DISABLED 0
#define SCROLL_MOUSE 0
#define SCROLL_KEYBOARD 1
int scrollingState = SCROLL_DISABLED;
int lastReading = 0;
int scrollType = SCROLL_KEYBOARD;
void setup() {
Serial.begin(baudRate);
pinMode(LED_BUILTIN, OUTPUT);
Wire.begin();
if (!MPR121.begin(MPR121_ADDR)) {
Serial.println("error setting up MPR121");
switch (MPR121.getError()) {
case NO_ERROR:
Serial.println("no error");
break;
case ADDRESS_UNKNOWN:
Serial.println("incorrect address");
break;
case READBACK_FAIL:
Serial.println("readback failure");
break;
case OVERCURRENT_FLAG:
Serial.println("overcurrent on REXT pin");
break;
case OUT_OF_RANGE:
Serial.println("electrode out of range");
break;
case NOT_INITED:
Serial.println("not initialised");
break;
default:
Serial.println("unknown error");
break;
}
while (1);
}
MPR121.updateAll();
Keyboard.begin();
Mouse.begin();
setupProximity();
}
void setupProximity() {
// slow down some of the MPR121 baseline filtering to avoid
// filtering out slow hand movements
MPR121.setRegister(NHDF, 0x01); //noise half delta (falling)
MPR121.setRegister(FDLF, 0x3F); //filter delay limit (falling)
}
void loop() {
MPR121.updateAll();
proximitySensor(SCROLL_ELECTRODE);
}
int getScrollingState() {
return scrollingState;
}
void toggleScrooling() {
if (scrollingState == SCROLL_ENABLED) {
scrollingState = SCROLL_DISABLED;
Serial.println("Scrolling disabled");
digitalWrite(LED_BUILTIN, LOW);
// when scrool by mosewheel ends where to click ?
// if (scrollType == SCROLL_MOUSE) {
// Mouse.click();
// }
} else {
scrollingState = SCROLL_ENABLED;
Serial.println("Scrolling enabled");
digitalWrite(LED_BUILTIN, HIGH);
}
}
void proximitySensor(int electrode) {
// read the difference between the measured baseline and the measured continuous data
int reading = MPR121.getBaselineData(electrode) - MPR121.getFilteredData(electrode);
if (lastReading < 100 && reading > 100) {
toggleScrooling();
}
lastReading = reading;
// print out the reading value for debug
Serial.print("Proximity electrode: ");
Serial.println(reading);
if (getScrollingState() == SCROLL_ENABLED) {
// constrain the reading between our low and high mapping values
unsigned int prox = constrain(reading, LOW_DIFF, HIGH_DIFF);
// implement a simple (IIR lowpass) smoothing filter
lastProx = (filterWeight * lastProx) + ((1 - filterWeight) * (float)prox);
// Serial.print("lastProx: "); Serial.println(lastProx);
if (lastProx > 0) {
if (lastProx < (HIGH_DIFF * 0.4) ) {
scroll(-1, lastProx);
// Serial.println("UP");
} else if (lastProx > (HIGH_DIFF * 0.6) ) {
scroll(1, lastProx);
// Serial.println("DOWN");
}
}
}
}
/*
scroll logic - 2 levels
100-60% proximity => direction down
0-40% porximity => direction up
*/
void scroll(int direction, float prox) {
if (prox > HIGH_DIFF / 2) {
prox = abs(prox - HIGH_DIFF);
}
// prox is now between 0-25
// do some smooth filter
prox = prox * (prox / 4) * 1.3;
if (scrollType == SCROLL_MOUSE) {
Mouse.move(0, 0, 1 * direction * -1);
delay(prox);
} else if (scrollType == SCROLL_KEYBOARD) {
if (directi 1) {
Keyboard.press(KEY_DOWN_ARROW);
} else {
Keyboard.press(KEY_UP_ARROW);
}
Keyboard.releaseAll();
delay(prox);
}
}
Really nice little sketch. I am learning a lot with it. I have used your code ( Thanks a lot ) it does work very well. What was strange is that I had to remove the following line in order for it to compile correctly: setupProximity() void setupProximity() { // slow down some of the MPR121 baseline filtering to avoid // filtering out slow hand movements MPR121.setRegister(NHDF, 0x01); //noise half delta (falling) MPR121.setRegister(FDLF, 0x3F); //filter delay limit (falling) }
ReplyDelete