Skip to content

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

References