04: Microcontroller Programming
For this week we had Arduino code that changed the speed of our kinetic sculpture with PWM. Decided to change it to documenting the software side of my final project (CubeControl) — the same write-up will go on the final project page later.
Two digital hall-effect sensors on D2 and D3 detect when magnets on the cube pass by. The hard part was telling swipe direction: same two sensors, but order matters.
Demo video
Recording of bidirectional swipe detection. One direction prints A -> B, the other B -> A.
Circuit schematic

Hall sensors on D2 and D3 with internal pull-ups. Magnets on the cube pull the line LOW when they get close.
Full sketch
Setup & pins
Both halls are digital inputs with INPUT_PULLUP. The sensors I had printed a one when it senses a magnet nearby, and 0 otherwise.
const int hallA = D2;
const int hallB = D3;
const unsigned long debounceMs = 8;
const unsigned long swipeTimeoutMs = 400;
void setup() {
Serial.begin(115200);
pinMode(hallA, INPUT_PULLUP);
pinMode(hallB, INPUT_PULLUP);
}No blocking delay
Early tests used delay(10) in the loop and spammed Serial. I missed fast swipes and this made the timing feel flunky. The final loop never blocks but it polls every pass and tracks when each raw reading last changed, and only accepts a new stable level after debounceMs (8 ms) without another flip. I debounce by time instead of delay code.
if (rawA != lastA) {
lastA = rawA;
lastAChange = now;
}
if (rawB != lastB) {
lastB = rawB;
lastBChange = now;
}
if ((now - lastAChange) > debounceMs && rawA != stableA) {
stableA = rawA;
if (stableA == LOW) A_fall = true;
}
if ((now - lastBChange) > debounceMs && rawB != stableB) {
stableB = rawB;
if (stableB == LOW) B_fall = true;
}This ended up being very reliable. Quick cube turns still registered clean A→B vs B→A strings on Serial without double-firing from bounce.
A_fall and B_fall
Each loop we reset two flags. A fall means sensor A just settled LOW (magnet hit A). Same for B. We only set the flag on a stable transition to LOW, not on every noisy read.
bool A_fall = false; bool B_fall = false; // ... debounce logic above sets A_fall / B_fall when stable goes LOW
One sensor firing alone starts a swipe. The second sensor firing completes it — that order is the direction.
Bidirectional swipe logic
firstSensor remembers who went first (1 = A, 2 = B). If nothing finishes within swipeTimeoutMs, we reset so a half-swipe doesn't stick around.
if (firstSensor != 0 && now - firstTime > swipeTimeoutMs) {
firstSensor = 0;
}
if (firstSensor == 0) {
if (A_fall && !B_fall) {
firstSensor = 1;
firstTime = now;
} else if (B_fall && !A_fall) {
firstSensor = 2;
firstTime = now;
}
} else if (firstSensor == 1 && B_fall) {
Serial.println("A -> B");
firstSensor = 0;
} else if (firstSensor == 2 && A_fall) {
Serial.println("B -> A");
firstSensor = 0;
}- A → B: A falls first and then B falls
- B → A: B falls first, then A (the other direction)