Input devices
Objectives
- Use I2c to read a sensor.
- Test capacitive touch with the SAMD21
Task
Group assignment:
- Probe an input device(s)'s analog levels and digital signals
- Document your work on the group work page and reflect on your individual page what you learned
Individual assignment:
- Measure something: add a sensor to a microcontroller board that you have designed and read it.
Group Assingment
Group assignment is here. I didn't contribute since I did the 2 previous ones.
Measuring with a sensor
I decided to use a digital Hall effect sensor to measure the position of a magnet attached to a bicycle spoke.. This will be part of one of my possible final projects, the Bonobo Lights.
This is how I set it up:
A detail of the magnet, attached with masking tape to the bicycle spoke:
First working version of the code. Click to show.
// For position estimation with exponential smoothing. In microseconds.
// Initialize durations to very high values (10^8us) so that there's
// no graphical updates until after 5 revolutions.
const int nRevolutionsToConsider = 5;
const int weights[nRevolutionsToConsider] = { 315, 625, 1250, 2500, 5000 }; // most recent weighs heavier
const int sumOfWeights = 9690;
long revolutionDurations[nRevolutionsToConsider] = { 100000000, 100000000, 100000000, 100000000, 100000000 };
long currentDurationOfRevolution = 100000000;
long revolutionStart;
// Barduino
const int HALL_SENSOR_PIN_DIGITAL = 1; // detection pulls down to 0
const int INDICATOR_PIN = 48; // Integrated LED
// Settings
const int INTERFACE_UPDATE = 100000; // microseconds
const float target = 0.1; // target position to light up, in fraction of a rev
// State machine
long start, now, step;
int reading_d, detections;
long revolution = 0;
bool pressed = false;
bool previouslyPressed = false;
long lastInterfaceUpdate;
void setup() {
pinMode(HALL_SENSOR_PIN_DIGITAL, INPUT);
pinMode(INDICATOR_PIN, OUTPUT);
Serial.begin(115200);
start = micros();
revolutionStart = start;
lastInterfaceUpdate = start;
step = 0;
revolution = 0;
pressed = true;
}
void loop() {
now = micros();
updateSensor(); // Every single turn of the loop
printSerial(); // On a fixed schedule
lightUp();
step += 1;
}
void lightUp() {
if (abs(currentPosition() - target) < 0.01) {
digitalWrite(INDICATOR_PIN, HIGH);
} else {
digitalWrite(INDICATOR_PIN, LOW);
}
}
void updateSensor() {
previouslyPressed = pressed;
pressed = !digitalRead(HALL_SENSOR_PIN_DIGITAL); // Sensor is pulled up
if (pressed && !previouslyPressed) {
updateSpeedEstimation();
revolution += 1;
}
}
// For now, just use the last turn. Later we can go to the exponential smoothing version
void updateSpeedEstimation() {
long now = micros();
currentDurationOfRevolution = now - revolutionStart;
revolutionStart = micros();
}
// Exponential smoothing for smoother estimation
// To be debugged
void exponentialSmoothing() {
long now = micros();
int offset = revolution % nRevolutionsToConsider; // circular buffer
revolutionDurations[offset] = now - revolutionStart;
revolutionStart = now;
long tempEstimation = 0;
for (int i = 0; i < nRevolutionsToConsider; i++) {
int whichWeight = (i + ((nRevolutionsToConsider - 1) - offset)) % nRevolutionsToConsider; // Black magic. Notes on Remarkable
tempEstimation += weights[whichWeight] * revolutionDurations[i];
}
currentDurationOfRevolution = tempEstimation / sumOfWeights;
}
// Fractions of a revolution
float currentPosition() {
double elapsed = micros() - revolutionStart;
return elapsed / currentDurationOfRevolution;
}
void printSerial() {
now = micros();
if (now - lastInterfaceUpdate > INTERFACE_UPDATE) {
Serial.printf("revolution:%d revolutionStart:%d current_postion:%.3f currentDurationOfRevolution:%d\n", revolution, revolutionStart, currentPosition(), currentDurationOfRevolution);
lastInterfaceUpdate = now;
}
}
I did a very simple test pattern: just light up an LED at a fixed position. Specifically, at 0.1 revolutions ahead of where the sensor is placed, in the fork. It works!!
Porting to board 02
After experimenting with the Barduino I set out to get it working on my development board, board 02. I reproduced the setup and got coding. The code listed below can also be found in my final project repo as well as in summary elements. I proceeded to set it up with the sensor, board, and LEDs on the fork and the magnet on the wheel, like I had done with the Barduino. This way I could keep the cable connected so that my feedback loop was as tight as possible.
Hall Effect sensor-based position estimation. Click to show code.
// For position estimation with exponential smoothing. In microseconds.
// Initialize durations to very high values (10^8us) so that there's
// no graphical updates until after 5 revolutions.
const int nRevolutionsToConsider = 5;
const int weights[nRevolutionsToConsider] = { 315, 625, 1250, 2500, 5000 }; // most recent weighs heavier
const int sumOfWeights = 9690;
long revolutionDurations[nRevolutionsToConsider] = { 100000000, 100000000, 100000000, 100000000, 100000000 };
long currentDurationOfRevolution = 100000000;
long revolutionStart;
// Barduino
const int HALL_SENSOR_PIN_DIGITAL = 1; // detection pulls down to 0
const int INDICATOR_PIN = 48; // Integrated LED
// Settings
const int INTERFACE_UPDATE = 100000; // microseconds
const float target = 0.1; // target position to light up, in fraction of a rev
// State machine
long start, now, step;
int reading_d, detections;
long revolution = 0;
bool pressed = false;
bool previouslyPressed = false;
long lastInterfaceUpdate;
void setup() {
pinMode(HALL_SENSOR_PIN_DIGITAL, INPUT);
pinMode(INDICATOR_PIN, OUTPUT);
Serial.begin(115200);
start = micros();
revolutionStart = start;
lastInterfaceUpdate = start;
step = 0;
revolution = 0;
pressed = true;
}
void loop() {
now = micros();
updateSensor(); // Every single turn of the loop
printSerial(); // On a fixed schedule
lightUp();
step += 1;
}
void lightUp() {
if (abs(currentPosition() - target) < 0.01) {
digitalWrite(INDICATOR_PIN, HIGH);
} else {
digitalWrite(INDICATOR_PIN, LOW);
}
}
void updateSensor() {
previouslyPressed = pressed;
pressed = !digitalRead(HALL_SENSOR_PIN_DIGITAL); // Sensor is pulled up
if (pressed && !previouslyPressed) {
updateSpeedEstimation();
revolution += 1;
}
}
// For now, just use the last turn. Later we can go to the exponential smoothing version
void updateSpeedEstimation() {
long now = micros();
currentDurationOfRevolution = now - revolutionStart;
revolutionStart = micros();
}
// Exponential smoothing for smoother estimation
// To be debugged
void exponentialSmoothing() {
long now = micros();
int offset = revolution % nRevolutionsToConsider; // circular buffer
revolutionDurations[offset] = now - revolutionStart;
revolutionStart = now;
long tempEstimation = 0;
for (int i = 0; i < nRevolutionsToConsider; i++) {
int whichWeight = (i + ((nRevolutionsToConsider - 1) - offset)) % nRevolutionsToConsider; // Black magic. Notes on Remarkable
tempEstimation += weights[whichWeight] * revolutionDurations[i];
}
currentDurationOfRevolution = tempEstimation / sumOfWeights;
}
// Fractions of a revolution
float currentPosition() {
double elapsed = micros() - revolutionStart;
return elapsed / currentDurationOfRevolution;
}
void printSerial() {
now = micros();
if (now - lastInterfaceUpdate > INTERFACE_UPDATE) {
Serial.printf("revolution:%d revolutionStart:%d current_postion:%.3f currentDurationOfRevolution:%d\n", revolution, revolutionStart, currentPosition(), currentDurationOfRevolution);
lastInterfaceUpdate = now;
}
}
It works fine.
Once it was working, I included a pattern and tested it mounted and in the dark. It worked great!!! That was a great moment.
Further ideas and pending TODOs
- Reproduce Multi-Touch Kit: A Do-It-Yourself Technique for Capacitive Multi-Touch Sensing Using a Commodity Microcontroller (pdf link)