It goes without saying that there are innumerable apps for smartphones that can be used to analyze a person's reaction time. However, as always, for no specific reason they require access to you phone book and contacts. Hence, you'd better go for offline devices.
In most reaction time measurement installations, a light or sound signal instructs the subject to respond in a certain way. However, in real-life situations like driving a car, you need to decide how to react, e.g. by pressing the brake pedal or possibly better by turning the steering wheel to avoid an accident.
More than simple: no (zero) external hardware neededThe simple trick is: every Arduino is equipped with a "sensor" called RESET button. In order to make use of it, you need to declare part of the memory as "noinit". So, when a sketch starts it can see the values from the previous run.
Just enter these few lines of code - and have fun:
/*
Measure your or somebody's Reaction time.
NO (zero) external hardware required, only
the computer to display the Serial monitor.
*/
float pi __attribute__ ((section (".noinit")));
boolean cheated __attribute__ ((section (".noinit")));
boolean v1 __attribute__ ((section (".noinit")));
long time1 __attribute__ ((section (".noinit")));
boolean v2 __attribute__ ((section (".noinit")));
long time2 __attribute__ ((section (".noinit")));
boolean coldStart = false;;
long start;
void setup() {
if (pi != PI) {
coldStart = true;
pi = PI;
}
Serial.begin(115200);
if (!coldStart) {
Serial.println("last measurement:");
if (cheated)
Serial.println("\n- C H E A T E D -\n");
else {
if (v1) Serial.print(time1);
else Serial.print(time2);
Serial.println(" ms.");
}
}
cheated = true;
Serial.println("New test:");
Serial.println("Press RESET on request");
delay(2000 + random(2000));
Serial.println("\n- P R E S S N O W - time is running");
start = millis();
cheated = false;
}
void loop() {
/*
possibly the user is pressing RESET exactly
when time is written to the memory.
That is why a "valid" flag is written
and the value is written twice.
Only one of them can be corrupted.
*/
v1 = false;
time1 = millis() - start;
v1 = true;
v2 = false;
time2 = millis() - start;
v2 = true;
}Now the ordinary versionUsing the Multi-Function-Shield makes these things extremely easy and cheap. For a few dollars you get the display, buttons, a buzzer, some extra LEDs and much more, all mounted on a single shield that fits nicely on top of the Arduino UNO.
The only thing to look out for is the USB port on the Arduino. Make sure you cover it with some tape to avoid producing any shortcuts as the shielding comes very low.
Actually, there already exists a library to control the display and buttons, but I decided to write one myself in order to make programming much easier, and called it MFS3. It handles text, integers up to 9999, and floating point values between 0.001 and 9999.0 properly. Leading zeros of integers will be suppressed. The print function is overloaded by an optional parameter to give a delay time for the user to read it. Unfortunately, not all the letters can be displayed using seven segments. You have to select texts that do not contain K, M, and W, and some letters look like numbers. But a double lower-case “ll” can be displayed on a single 7-segment digit.
As the Multi-Function-Shield uses the SN74HC595 instead of the TM1637 chip you need to handle the multiplex driving of the digits by software. This was done by using Timer0, so you are free to use all the remaining timers if you want to.
When started, the program asks if sound support is required because when many students would be using it in class a lot of sounds would be disturbing. Then after a greeting message the text “Subt” is displayed. The minus-character is the only math operation that can be shown by 7-segment displays.
The task to solve could be anything between “1 – 0” to “12 – 9” but only results 1, 2, or 3 are valid. So, 30 different tasks can be presented. When a key was pressed it gets checked for correctness, either Good or bad is shown. Then the reaction time will be displayed.
If no key was pressed before the timeout it gives up and starts a new task.
If a key was pressed before the task was displayed an error message will be displayed and it also starts a new task.
The number of the current task is shown using the four small LEDs in binary code.
After ten trials the total results will be displayed, and a new sequence will start.
/*
(C) 2021 Klaus J. Koch - Marburg
prints easy calculation tasks on LEDs
and records time for the answer.
After 10 trials the average is displayed.
*/
#include <MFS3.h>
// texts:
// replace texts by words in your favourite language
char SOUND[] = "tone";
char YES[] = "yes1";
char NO[] = "no 2";
char HELLO[] = "HE\\o"; // there is a code for "LL"
char SUBTRACT[] = "subt";
char ERROR[] = "Oops";
char TOO_LATE[] = "dead";
char GOOD[] = "Good";
char BAD[] = "bad ";
char CONTINUE[] = "cont";
char AVERAGE[] = "avrg";
MFS3 mfs;
boolean sndOn = false;
void setup() {
Serial.begin(9600);
Serial.println(__FILE__);
randomSeed(analogRead(A6));
mfs.begin();
sndOn = askSnd();
}
char buff[4];
int count = 1;
int cntOk = 0;
float sum = 0;
void loop() {
mfs.D4(count & 1);
mfs.D3(count & 2);
mfs.D2(count & 4);
mfs.D1(count & 8);
mfs.print(HELLO, 1000); // welcome
mfs.print(SUBTRACT);
/* what is \\?
this is the code 0x5C for
double lower-case "L"
*/
float now = second();
float startTime = now + random(3, 10);
boolean keyPressed = false;
byte response = 0;
while (second() < startTime) {
response = mfs.S123();
if (response) keyPressed = true;
}
// now the startTime is reached.
if (keyPressed) {
// pressed too early!!!
mfs.print(ERROR, 1000);
return;
// new trial
}
// print request + sound on:
int result = makeNewTask();
mfs.print(buff);
if (sndOn) mfs.tone(1);
float tooLate = second() + 10;
// wait for key:
while (!response) {
// maximum time exceeded?
if (second() > tooLate) {
mfs.tone(0);
mfs.print(TOO_LATE, 5000);
return;
}
response = mfs.S123();
}
// key has been pressed
mfs.tone(0);
float whenPressed = second();
// calculate zhe difference:
float reactionTime = whenPressed - startTime;
// was the answer correct?
if (response == (1 << result)) {
mfs.print(GOOD, 1000);
cntOk++;
sum = sum + reactionTime;
}
else mfs.print(BAD, 1000);
// show reactionTime:
mfs.print(reactionTime, 1E3);
// continue?
mfs.print(CONTINUE, 2000);
count++;
if (count > 10) {
// evaluation:
mfs.print(GOOD, 1000);
mfs.print(cntOk, 1000);
if (cntOk > 0) {
mfs.print(AVERAGE, 2000);
float mean = sum / cntOk;
mfs.print(mean, 5E3);
}
count = 1;
cntOk = 0;
}
}
boolean askSnd() {
const char * msg[] = {SOUND, YES, NO};
int i = 0;
while (true) {
mfs.print(msg[i]);
// disp this msg for 1 second:
unsigned long t = millis() + 1000;
while (millis() < t) {
switch (mfs.S123()) {
case 2: return true;
case 4: return false;
}
}
// next msg:
if (++i > 2) i = 0;
}
}
float second() {
return millis() * 0.001;
}
int makeNewTask() {
// new task: a - b = c
int b = random(10); // 0 to 9
int c = random(3) + 1; // 1 to 3
int a = b + c; // 1 to 12
if (a < 10) buff[0] = ' ';
else buff[0] = '1';
buff[1] = '0' + (a % 10);
buff[2] = '-';
buff[3] = '0' + b;
// in buff the new task is stored
return c;
}And this is the flow diagram of the sketch:


_ztBMuBhMHo.jpg?auto=compress%2Cformat&w=48&h=48&fit=fill&bg=ffffff)


Comments