A counter that learns
A counter for marks going past: black tape on a belt, a slot in a wheel, a hand waved over the block. At start it learns the background and the mark, then counts a mark once the reading has gone 60 % of the way between them and re-arms only when it comes back past 40 %. Two lines, not one, because an analog reading wobbles at every edge.
Learn the two readings
A threshold typed into a sketch is right for one surface, one height and one room. So this sketch measures instead. When it starts it asks you to hold the sensor over the background for three seconds, averages fifty readings, then asks for the mark and does the same. From then on it only asks one question of each reading: how far has it gone, from the background towards the mark? 0 is the background, 1 is the mark.
Mount the block face down, the domes 2 to 5 mm over whatever passes. Hold that height while it learns.
One mark, one count
Press Play with each sketch. As the edge of a mark crosses the sensor, part of the receiver's view is dark and part is pale, and the reading wobbles on its way from one to the other. A single threshold in the middle catches every wobble that crosses it. Two lines do not: the sketch counts once the reading passes 60 %, and will not count again until it has fallen back below 40 %. The wobble at an edge never spans both. That gap between the lines is hysteresis. The trace is drawn to show the shape, not measured.
How the sketch does it
learn() prints its prompt, waits three seconds, and averages fifty
readings. progress() turns a reading into the fraction of the way from
background to mark, and because it divides by mark - background, it
does not care which one is higher. loop() holds one flag, onMark: it
counts and sets it on passing ENTER, and clears it on passing LEAVE.
There is no delay() in loop(), so it reads as fast as the ADC allows.
If the two learnt readings are within 40 counts of each other, the sketch says so: there is not enough contrast to count on.
What you should see
At 115200: Hold it over the background ..., then Hold it over the mark to count ..., then the two readings it learnt. Each mark that passes after
that prints count 1, count 2, and so on, once each however slowly it
moves.
How fast it can count
The receiver can switch far faster than anything you will wave past it. The limit is on the board: when a mark leaves, SIGNAL climbs back through the 4.7 kΩ into the 100 nF, a time constant of 0.47 ms. It is 90 % of the way back after about 1.1 ms. A gap narrower than that, at the speed it passes, never gets the reading back to the line. For a disc with slots, that is still hundreds of slots a second.
The code
Learns two readings at start, three seconds each: the background, then the mark. Each loop takes the mean of eight readings and works out how far it has gone from one to the other; it counts on passing ENTER and re-arms on falling back past LEAVE. No delay() in loop().
/*
Reflective Optical Sensor - a counter TK57 / /p/tk57
Wiring. Count from the square pad on the TinkerBlock board, parts
up, header at the bottom:
GND -> GND
VCC -> 3V3 on an ESP32, ESP32-S3 or Pico; 5V on an Uno.
SIGNAL rises to VCC over a dark surface, so VCC
is the voltage your board's pins run at.
NC -> nothing (unconnected on the board)
SIGNAL -> GPIO 4 on an ESP32-S3, GPIO 34 on an ESP32,
A0 on an Uno, GP26 on a Raspberry Pi Pico
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
Mount the block face down, 2 to 5 mm over the surface. It learns
two readings at start: the background, then the mark to count.
*/
// The analog pin SIGNAL is wired to.
// Uno: A0. ESP32: 34. ESP32-S3: 4. Pico: 26.
const int SENSOR_PIN = 4;
// Two lines, not one: a mark counts when the reading passes
// ENTER of the way from background to mark, and the counter
// re-arms only when it falls back past LEAVE.
const float ENTER = 0.6;
const float LEAVE = 0.4;
int background;
int mark;
bool onMark = false;
unsigned long count = 0;
int readAverage() {
long sum = 0;
for (int i = 0; i < 8; i++) sum += analogRead(SENSOR_PIN);
return sum / 8;
}
int learn(const char *what) {
Serial.print("Hold it over ");
Serial.print(what);
Serial.println(" ...");
delay(3000);
long sum = 0;
for (int i = 0; i < 50; i++) {
sum += readAverage();
delay(10);
}
return sum / 50;
}
// How far a reading has gone from background towards mark.
float progress(int level) {
return float(level - background) / float(mark - background);
}
void setup() {
Serial.begin(115200);
delay(1000);
background = learn("the background");
mark = learn("the mark to count");
Serial.print("background ");
Serial.print(background);
Serial.print(", mark ");
Serial.println(mark);
if (abs(mark - background) < 40) {
Serial.println("Too alike to tell apart: press reset.");
}
}
void loop() {
float p = progress(readAverage());
if (!onMark && p >= ENTER) {
onMark = true;
count++;
Serial.print("count ");
Serial.println(count);
} else if (onMark && p <= LEAVE) {
onMark = false; // back on the background: re-armed
}
}It works whichever way round the contrast is, a dark mark on a pale belt or a pale one on a dark belt, because it measures the way from background to mark. The sketch compiles for an ESP32-S3 and an Uno.
View on GitHub · blocks/tk57-reflective-optical-sensor/arduino/optical_counter/optical_counter.ino @ v1.5When it does not work
The reading wobbled across the line as the mark's edge passed. Keep the two lines apart: ENTER at 0.6 and LEAVE at 0.4 of the way from background to mark. If it still double-counts, move them further apart, to 0.7 and 0.3.
The background and the mark look the same to the sensor in infrared. Bring it closer, 2 to 5 mm, and use matte black tape or a cut-out slot rather than printed ink. Then press reset and let it learn again.
SIGNAL climbs back through a 4.7 kΩ into 100 nF, a time constant of 0.47 ms: it needs about a millisecond to get most of the way. A gap that passes the sensor faster than that never reaches the line. Slow the belt, or widen the gaps.
The learnt readings include the room's infrared. A lamp switched on, or the sun moving, shifts both. Shade the sensor and run the calibration where the counter will work.
Always at the top, always at the bottom, or wandering: where to look.
When the reading is wrong →Edit this page — content/books/reflective-optical-sensor/a-counter-that-learns.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Reflective Optical Sensor
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.