/* Lightbulb Toggle — Arduino sketch By gllbhh for Fab Academy Physical button (D7 / GPIO 1) and Processing GUI toggle keep the LED (D6 / GPIO 0) in sync over serial. How it works ------------ - Pressing the button toggles the LED and sends the new state to the PC. - The PC can send a state at any time to set the LED directly. - Neither side echoes back after receiving a command, so the two ends can never cause each other to loop indefinitely. Serial protocol Arduino → PC : "0,\n" 0 = off, 1 = on Serial protocol PC → Arduino : "0,\n" 0 = off, 1 = on Wiring (breakout PCB) --------------------- LED_PIN GPIO 0 D6 — LED with current-limiting resistor, active HIGH BTN_PIN GPIO 1 D7 — button to GND, internal pull-up enabled */ #define LED_PIN 0 // D6 — LED output #define BTN_PIN 1 // D7 — button input (pulled HIGH, LOW when pressed) // Tracks the current LED state so we can toggle it and report it. bool ledState = false; // Debounce variables — we keep two readings: // lastReading : what digitalRead() returned on the previous loop iteration // btnState : the reading that has been stable for at least DEBOUNCE_MS // When lastReading differs from the new reading we reset the debounce timer. // Only when the stable reading (btnState) finally changes do we act on it. int lastReading = HIGH; int btnState = HIGH; unsigned long lastDebounce = 0; // timestamp of the last pin change const int DEBOUNCE_MS = 50; // ms a reading must be stable to count void setup() { Serial.begin(9600); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); // LED off at startup // INPUT_PULLUP means the pin reads HIGH normally and LOW when the button // connects it to GND — so LOW = pressed. pinMode(BTN_PIN, INPUT); } void loop() { // ---------------------------------------------------------------- // 1. Debounced button edge-detect // ---------------------------------------------------------------- // Read the raw pin voltage every loop. Mechanical buttons bounce // (rapidly switch on/off) for a few milliseconds after being pressed. // We ignore changes that settle in less than DEBOUNCE_MS. int reading = digitalRead(BTN_PIN); // Any change at all restarts the debounce timer. if (reading != lastReading) { lastDebounce = millis(); } // Only act once the reading has been stable for the full debounce window. if (millis() - lastDebounce > DEBOUNCE_MS && reading != btnState) { btnState = reading; // accept the new stable state if (btnState == HIGH) { // LOW = button just pressed (falling edge) ledState = !ledState; // flip the LED digitalWrite(LED_PIN, ledState); // Tell the PC about the new state so the GUI stays in sync. // Format: "0,\n" — key 0 = LED channel, value 0 or 1. Serial.println("0," + String(ledState ? 1 : 0)); } // We intentionally ignore the rising edge (button release) — we only // want a single toggle per press, not one for press and one for release. } lastReading = reading; // remember for next loop iteration // ---------------------------------------------------------------- // 2. Receive a command from the PC // ---------------------------------------------------------------- // Serial.available() returns the number of bytes waiting in the buffer. // We only read when there is something to avoid blocking. if (Serial.available()) { char buf[32]; // Read bytes until '\n' (the line terminator Processing sends). // readBytesUntil does NOT add a null terminator, so we do it manually. int n = Serial.readBytesUntil('\n', buf, 31); buf[n] = '\0'; // null-terminate so string functions work // Parse "key,value" — atoi() converts the leading integer and stops at ','. int key = atoi(buf); char* comma = strchr(buf, ','); // find the separator; NULL if absent // We only handle key 0 (the LED channel). Ignore malformed messages // (no comma) or unknown keys. if (comma && key == 0) { // comma + 1 points to the character right after the comma. ledState = atoi(comma + 1) == 1; digitalWrite(LED_PIN, ledState); // No echo back to the PC — the GUI updated itself optimistically // the moment the user clicked, so sending a confirmation would // just create an unnecessary round-trip (and could cause a loop). } } }