ask:
show that you can make a simple interactive system with physical controls, and that you can keep a
userperson engaged with your system.sub asks: On a technical level, though, your project should show that you understand digital input and output, analog input and output, serial communication, and good physical interaction design principles. On a conceptual level, your project should help people to enjoy whatever setting it is designed for.
source: https://itp.nyu.edu/physcomp/syllabus/assignments/#Midterm_Project
thought:
dahiya tore-down my burgeoning prejudice against recent forms of first word art: when i tried to criticise AR/VR, he cut me off, pointed at a light-switch, and wondered how ‘flicking a mechanical switch to fire electrons that illuminate a bulb far away’ is no less crazy than ‘wearing a virtual-reality headset’.
we frequently interact with electricity, but its magic is too quick to comprehend. there is a lot that we still don’t understand 1, but that doesn’t stop us from consuming 24,398,000,000,000,000 watt-hours of electrical-energy every year2 .
the idea was to communicate this magic — borrowed from dahiya’s humility, coupled with my own curiosity — by showing how much laborious work electrons do, to turn on a simple led.
![]() | ![]() |
|---|---|
study:
this line of enquiry originally began with reading practical electronics for inventors, as i realised that electrons flow opposite to the direction of conventional current flow.
i then saw more videos, this one being one the clearest ones about the topic: https://www.youtube.com/watch?v=3KePcASD0NQ
thinking-through + prototyping:
i wanted to do this project well. intro-to-fabrication taught me the importance of spending more time thinking through the thing i’m making than making it. so, i applied that:
on first glance, it was meant to look simple & neat. the person should know how to interact with it. via said interaction, a person should be able to leave with a sense of appreciation for all the electrical-hardwork around them.
i first thought about the components & their layout. i’ve now grown up to understand that tiny choices compound to bigger outcomes.


i chose to lay out components straight, as opposed to the conventional circle to communicate flow, since the ‘electrons’ have to be pushed into the next component. i also added the capacitor, to show electromotive-force gradually reduce through the circuit, as the capacitor gives off its stored current (when the power source is turned off).
then, i planned out the arrangement of the components (both visible & invisible).

while this started off as a single interaction, prototyping showed me the opportunity to add several more. i then thought through all of my interactions, in time:

i also tested material for light-diffusion. realised that 3d printed material does interesting light diffusion.
phil suggested the following materials:
- polystyrene sheet: 1mm
- white translucent acrylic sheet: 1-3mm
- light pipe from colourless acrylic rod to make leds stay at the bottom but the light to show above.
i will go check these out later. my next step will be to program the electron-leds.
251009:
i needed to program the leds.
the first problem i run into is that i need to send analog-output to ~30-35 leds. arduino allows me to send analog outputs to 7, and digital to 20.

looked at something called a mux or multiplexor. think of it like a motor-driver, but uses 4 pins to give out 16 outputs (based on varying combinations of highs & lows).

pedro then told me about the multiplexors that we have in the shop, as consumables. we have the cd4051be multiplexors, that do the job that i want.


nikolai helped me understand these.
then, with octavio, we figured out how to make it work (because my circuit didn’t work — i thought the inhibit-pin could do with no signal, but it needed a low-signal).
once i knew that this configuration worked, i sketched out the schematic too; to save it.

next, i began programming the leds.
i first began to understand the concept of pointers, in comparison to javascript.
in javascript, arrays are objects (therefore, callable by their name, and having methods (such as .length, et-cetera)). in c, arrays are blocks of memory. when i declare an array in c:
int arr [] = {0,1,2}; sarr is a pointer to the memory address of the first element in the array, and knows nothing about the array itself (because all the other elements are after it). when we declare an array, cpp automatically decays the array (converts to pointer of the first element).
i can’t believe i have to learn another programming language. argh.
anyway, this block of code returns the size of an array, when called with an array-name passed into it.
//arrays in c work weirdly. you can't directly call an array, but call the reference of that array.
//a template is used to define a 'generic' function. see more here: https://www.geeksforgeeks.org/cpp/templates-cpp/
//it accepts a typename, and an unsigned integer for size:
template <typename T, size_t N>
//when i use this, the array is not decayed to a pointer, and n returns the size of it.
int get_array_length (T(&)[N]){
return N;
}i need this to loop through the array, as i pass values.
started from scratch. cleaned up the circuit that’s supposed to stay fixed; with solid-wires.

wrote simple code to test everything.
//test mpx.
int a = 14;
int b = 15;
int c = 16;
int inh = 17;
void setup() {
// put your setup code here, to run once:
pinMode (a, OUTPUT);
pinMode (b, OUTPUT);
pinMode (c, OUTPUT);
pinMode (inh, OUTPUT);
digitalWrite (inh, LOW);
}
void loop() {
turn_on(HIGH, LOW, LOW);
delay (1000);
turn_off();
delay (1000);
turn_on(LOW, HIGH, LOW);
delay (1000);
turn_off();
delay (1000);
}
void turn_off(){
digitalWrite (a, LOW);
digitalWrite (b, LOW);
digitalWrite (c, LOW);
}
void turn_on(int a_val, int b_val, int c_val){
digitalWrite (a, a_val);
digitalWrite (b, b_val);
digitalWrite (c, c_val);
}
since i’m new to cpp, i’ll follow what mimi yin makes students do in intro-to-computational-media — hard-code values, then begin to abstract.
what follows can be thought of as github commits.
replaced individual pin numbers with an array:
//multiple leds test.
int input_pins[] = { 14, 15, 16 };
int inh_pin = 17;
// int a = 14;
// int b = 15;
// int c = 16;
//helper to get array_size, when called during compile:
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
void setup() {
//set pin-modes, and default for inhibit.
int input_pins_length = get_array_length(input_pins);
for (int i = 0; i < input_pins_length; i++) {
pinMode(input_pins[i], OUTPUT);
}
pinMode(inh_pin, OUTPUT);
digitalWrite(inh_pin, LOW);
}
void loop() {
turn_on(HIGH, LOW, LOW);
delay(1000);
turn_off();
delay(1000);
turn_on(LOW, HIGH, LOW);
delay(1000);
turn_off();
delay(1000);
}
void turn_off() {
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
}
void turn_on(int a_val, int b_val, int c_val) {
digitalWrite(input_pins[0], a_val);
digitalWrite(input_pins[1], b_val);
digitalWrite(input_pins[2], c_val);
}achieved multiple blinks.
//multiple leds test.
/*
truth table for CD4051B:
[a,b,c] [high || low] = on_channel (source: datasheet)
[LOW, LOW, LOW] = 0;
[HIGH, LOW, LOW] = 1;
[LOW, HIGH, LOW] = 2;
[HIGH, HIGH, LOW] = 3
[LOW, LOW, HIGH] = 4;
[HIGH, LOW, HIGH] = 5;
[LOW, HIGH, HIGH] = 6;
[HIGH, HIGH, HIGH] = 7;
INHIBIT == HIGH = NONE;
*/
int input_pins[] = { 14, 15, 16 };
int inh_pin = 17;
//helper to get array_size, when called during compile:
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
void setup() {
//set pin-modes, and default for inhibit.
int input_pins_length = get_array_length(input_pins);
for (int i = 0; i < input_pins_length; i++) {
pinMode(input_pins[i], OUTPUT);
}
pinMode(inh_pin, OUTPUT);
digitalWrite(inh_pin, LOW);
}
void loop() {
blink(HIGH, LOW, LOW, 1000);
blink(LOW, HIGH, LOW, 1000);
blink(HIGH, HIGH, LOW, 1000);
}
void blink(int a_val, int b_val, int c_val, int time){
digitalWrite(input_pins[0], a_val);
digitalWrite(input_pins[1], b_val);
digitalWrite(input_pins[2], c_val);
delay (time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delay (time);
}
// void turn_on(int a_val, int b_val, int c_val) {
// digitalWrite(input_pins[0], a_val);
// digitalWrite(input_pins[1], b_val);
// digitalWrite(input_pins[2], c_val);
// }
// void turn_off() {
// digitalWrite(input_pins[0], LOW);
// digitalWrite(input_pins[1], LOW);
// digitalWrite(input_pins[2], LOW);
// }
made blink accept an array, instead of individual values.
//multiple leds test.
//include a cpp library, that allows functions to accept a list of values.
#include <initializer_list>
/*
truth table for CD4051B:
[a,b,c] [high || low] = on_channel (source: datasheet)
[LOW, LOW, LOW] = 0;
[HIGH, LOW, LOW] = 1;
[LOW, HIGH, LOW] = 2;
[HIGH, HIGH, LOW] = 3
[LOW, LOW, HIGH] = 4;
[HIGH, LOW, HIGH] = 5;
[LOW, HIGH, HIGH] = 6;
[HIGH, HIGH, HIGH] = 7;
INHIBIT == HIGH = NONE;
*/
int input_pins[] = { 14, 15, 16 };
int inh_pin = 17;
//helper to get array_size, when called during compile:
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
void setup() {
//set pin-modes, and default for inhibit.
int input_pins_length = get_array_length(input_pins);
for (int i = 0; i < input_pins_length; i++) {
pinMode(input_pins[i], OUTPUT);
}
pinMode(inh_pin, OUTPUT);
digitalWrite(inh_pin, LOW);
}
void loop() {
blink({ HIGH, LOW, LOW }, 1000);
blink({ LOW, HIGH, LOW }, 1000);
}
//blink is generated by chat-gpt. i couldn't figure it out.
void blink(std::initializer_list<int> values, int time_ms) {
int i = 0;
for (int val : values) {
if (i >= 3) break;
digitalWrite(input_pins[i], val);
i++;
}
delay(time_ms);
for (int j = 0; j < 3; j++) {
digitalWrite(input_pins[j], LOW);
}
delay(time_ms);
}it failed here:
//multiple leds test.
//include a cpp library, that allows functions to accept a list of values.
#include <initializer_list>
#include <math.h> // for fmod
/*
truth table for CD4051B:
[a,b,c] [high || low] = on_channel (source: datasheet)
[LOW, LOW, LOW] = 0;
[HIGH, LOW, LOW] = 1;
[LOW, HIGH, LOW] = 2;
[HIGH, HIGH, LOW] = 3
[LOW, LOW, HIGH] = 4;
[HIGH, LOW, HIGH] = 5;
[LOW, HIGH, HIGH] = 6;
[HIGH, HIGH, HIGH] = 7;
INHIBIT == HIGH = NONE;
*/
int input_pins[] = { 14, 15, 16 };
int inh_pin = 17;
//helper to return high / low, based on time & frequency:
int squareWaveSignal(float frequency) {
// Get the time in microseconds
unsigned long t = micros();
// Calculate the period in microseconds
float period_us = 1e6 / frequency;
// Square wave: toggle every half period
return (fmod(t, period_us) < (period_us / 2)) ? HIGH : LOW;
}
//helper to get array_size, when called during compile:
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
void setup() {
//set pin-modes, and default for inhibit.
int input_pins_length = get_array_length(input_pins);
for (int i = 0; i < input_pins_length; i++) {
pinMode(input_pins[i], OUTPUT);
}
pinMode(inh_pin, OUTPUT);
digitalWrite(inh_pin, LOW);
}
void loop() {
blink({ squareWaveSignal(2), LOW, LOW }, 100);
}
//blink is generated by chat-gpt. i couldn't figure it out.
void blink(std::initializer_list<int> values, int time_ms) {
int i = 0;
for (int val : values) {
if (i >= 3) break;
digitalWrite(input_pins[i], val);
i++;
}
delay(time_ms);
for (int j = 0; j < 3; j++) {
digitalWrite(input_pins[j], LOW);
}
delay(time_ms);
}this is why i don’t like chatgpt.
i’ll start from scratch, and instead of making the code reusable, i’ll hard code it — to make it work for my use-case first. then abstract it.
tom told me not to send analog signals, and just use the arduino to turn the leds on / off. i know that this won’t produce the effect that i want, and i wanted to get this concept down properly.
so, i built a small circuit and took it home with me; to spend a couple of hours in the night programming it.

i found some stuff on the arduino’s reference page that first re-enforced correct terminologies (w.r.t. digital, analog & pwm; covered in electrical components).
this video by jeffrey made me realise that i could use tone() to power up an led too. which then means that i can send pseudo-analog signals using the makeTone() lab that we did a while back.
i did do that. i think — i think — i can make it dim too. i just need to pass a different frequency over time.
it didn’t work. i tried this:
//send pwm signal to led via a digital output pin.
int led_pin = 9;
void setup() {
pinMode(led_pin, OUTPUT);
}
void loop() {
for (int i = 0; i < 200; i+=0.0000005) {
makeTone(i);
delay(1000);
}
}
void makeTone(float frequency) {
// set the period in microseconds:
int period = (1 / frequency) * 1000000;
// int period = (1 / frequency) * 5000000; //i want this to play over, say, 10 seconds.
// turn the speaker on:
digitalWrite(led_pin, HIGH);
// delay half the period:
delayMicroseconds(period / 2);
// turn the speaker off:
digitalWrite(led_pin, LOW);
// delay half the period:
delayMicroseconds(period / 2);
}
but, i knew that theoretically this should be possible. i tried to use chatgpt — and it gave me code that worked.
//send pwm signal to led via a digital output pin.
int led_pin = 9; // Pin connected to the LED
void setup() {
pinMode(led_pin, OUTPUT); // Set LED pin as an output
}
void loop() {
// Fade in the LED from 0 to 255 (off to full brightness)
for (int brightness = 0; brightness <= 255; brightness++) {
// Calculate the on time for the LED
int onTime = (brightness * 10); // The higher the brightness, the longer it stays on
int offTime = 2550 - onTime; // The rest of the period is the off time
// Send PWM signal by adjusting on and off times
digitalWrite(led_pin, HIGH); // Turn the LED on
delayMicroseconds(onTime); // Delay for the "on" time
digitalWrite(led_pin, LOW); // Turn the LED off
delayMicroseconds(offTime); // Delay for the "off" time
}
// Fade out the LED from 255 to 0 (full brightness to off)
for (int brightness = 255; brightness >= 0; brightness--) {
// Calculate the on time for the LED
int onTime = (brightness * 10); // The higher the brightness, the longer it stays on
int offTime = 2550 - onTime; // The rest of the period is the off time
// Send PWM signal by adjusting on and off times
digitalWrite(led_pin, HIGH); // Turn the LED on
delayMicroseconds(onTime); // Delay for the "on" time
digitalWrite(led_pin, LOW); // Turn the LED off
delayMicroseconds(offTime); // Delay for the "off" time
}
}but that isn’t fun. i then asked it to explain what it produced, and i attempted to understand & rewrite it myself.
so the first thing i understood was this:
PWM is a technique where you rapidly turn a device (like an LED) on and off. The duty cycle determines how long the device stays on vs. off. The human eye can’t distinguish the rapid switching, but it sees the average brightness.
which means that i had to essentially increase the duty-cycle over time.
the magic kind of happens here:
for (int brightness = 0; brightness <= 255; brightness++) {
// Calculate the on time for the LED
int onTime = (brightness * 10); // The higher the brightness, the longer it stays on
int offTime = 2550 - onTime; // The rest of the period is the off time
// Send PWM signal by adjusting on and off times
digitalWrite(led_pin, HIGH); // Turn the LED on
delayMicroseconds(onTime); // Delay for the "on" time
digitalWrite(led_pin, LOW); // Turn the LED off
delayMicroseconds(offTime); // Delay for the "off" time
}If brightness = 0, the LED will stay on for 0 * 10 = 0 microseconds (i.e., it won’t be on at all).
If brightness = 255, the LED will stay on for 255 * 10 = 2550 microseconds.
here’s my version of the code, with comments and a video of it working.
//send pwm signal to led via a digital output pin.
int led_pin = 9;
void setup() {
Serial.begin(9600);
pinMode(led_pin, OUTPUT);
}
const int max_brightness = 255;
const int interval = 30;
void loop() {
//fade in the led:
for (int b = 0; b <= 255; b++) {
int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
//send pseudo-pwm signal:
digitalWrite(led_pin, HIGH); //turn on.
delayMicroseconds(on_time); //for on_time.
digitalWrite(led_pin, LOW); //turn off for off_time.
delayMicroseconds(off_time); //for off_time.
}
//when the above is completed, we fade out the led:
//fade out the led:
for (int b = 255; b >= 0; b--) {
int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
//send pseudo-pwm signal:
digitalWrite(led_pin, HIGH); //turn on.
delayMicroseconds(on_time); //for on_time.
digitalWrite(led_pin, LOW); //turn off for off_time.
delayMicroseconds(off_time); //for off_time.
}
}isn’t this a way to convert literally all digital pins into analog ones?
good day night. now, i have to figure out how to pass this to the multiplexor. i might even try sending analog-signal to it (but i remember thinking that i needed to do this (pseudo-analog) because the mpx we have does not accept analog signals). but let’s see :)
christina tang explained a bunch of things about light to me.

she explained that while the computer sends out signal linearly, the eye doesn’t quite perceive it similarly. this is the difference between computation & perception that mimi yin was also referring to; in my conversation with mimi.
we spoke about photopic curves too.

we also discussed that my use of tone should perhaps dim the led too. octavio explained that the problem was with my delays in makeTone():
void makeTone(float frequency) {
// set the period in microseconds:
int period = (1 / frequency) * 1000000;
// int period = (1 / frequency) * 5000000; //i want this to play over, say, 10 seconds.
// turn the speaker on:
digitalWrite(led_pin, HIGH);
// delay half the period:
delayMicroseconds(period / 2);
// turn the speaker off:
digitalWrite(led_pin, LOW);
// delay half the period:
delayMicroseconds(period / 2);
}they’re on for 50% and off for 50%.
and that’s what i solved with the other function that i wrote. i think i’m going to try a version with makeTone() also, at some point.
christina tang also gave me programmable leds. but i don’t know — i think i want to go into the weeds of this one(manual leds), and fail if i do. i’m learning far too much and getting more comfortable with the medium.
i’ll resort back to the leds if i cannot get it to work. i have a bunch of ideas right now — what if i pass pwms to a digital multiplexor? tom also shared a pwm-driver.
while reading there are no electrons, the narrative came to me:

it would be amazing to show this narrative as the experience happens (via engraved text that is visible with leds underneath). however, this is the most aspirational version of my project. i now know, after 6 weeks, that this may not be what gets produced. i am tightly dependent on what i am able to technically achieve.
so, i’ll park this aspiration for now; and focus on making things work first — and then deciding what i can achieve.
the next thing i tried was to test the mux. can i send a pwm to the multiplexor’s input — and does that fade an led?
so, first i sent a HIGH signal to the multiplexor’s communication input from the arduino digital-output-pin, instead of taking 3.3 from the bus. however, the led was dim.
i also checked voltage-differences between what was being sent and what was being outputted. there isn’t a lot; so, i assume it’s current.
i assume that the current being passed is too little; via the multiplexor. this means that the multiplexor shouldn’t be used to power the led, but just control the pulse (i.e, i can pin this to ground). realised i’d have to use transistors for this. that’ll be 37 transistors. argh.
now my next hope is to pass a pwm instead of high to the multiplexor’s input.
it works!!!
i sent a pseudo-pwm via a digitalWrite pin to fade in an led. this means that i have infinite ports to fade in leds. this is amazing.
//multiple leds test.
/*
truth table for CD4051B:
[a,b,c] [high || low] = on_channel (source: datasheet)
[LOW, LOW, LOW] = 0;
[HIGH, LOW, LOW] = 1;
[LOW, HIGH, LOW] = 2;
[HIGH, HIGH, LOW] = 3
[LOW, LOW, HIGH] = 4;
[HIGH, LOW, HIGH] = 5;
[LOW, HIGH, HIGH] = 6;
[HIGH, HIGH, HIGH] = 7;
INHIBIT == HIGH = NONE;
*/
int input_pins[] = { 14, 15, 16 };
int inh_pin = 17;
int ctrl_pin = 18;
//helper to get array_size, when called during compile:
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
void setup() {
//set pin-modes, and default for inhibit.
int input_pins_length = get_array_length(input_pins);
for (int i = 0; i < input_pins_length; i++) {
pinMode(input_pins[i], OUTPUT);
}
pinMode(inh_pin, OUTPUT);
digitalWrite(inh_pin, LOW);
}
void loop() {
fade_signal(HIGH, LOW, LOW, 30);
}
void fade_signal(int val1, int val2, int val3, const int interval) {
const int max_brightness = 255;
//fade in the led:
for (int b = 0; b <= 255; b++) {
int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
//send pseudo-pwm signal:
digitalWrite(input_pins[0], val1); //turn on.
digitalWrite(input_pins[1], val2); //turn on.
digitalWrite(input_pins[2], val3); //turn on.
delayMicroseconds(on_time); //for on_time.
digitalWrite(input_pins[0], LOW); //turn on.
digitalWrite(input_pins[1], LOW); //turn on.
digitalWrite(input_pins[2], LOW); //turn on.
delayMicroseconds(off_time); //for off_time.
}
//when the above is completed, we fade out the led:
//fade out the led:
for (int b = 255; b >= 0; b--) {
int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
//send pseudo-pwm signal:
digitalWrite(input_pins[0], val1); //turn on.
digitalWrite(input_pins[1], val2); //turn on.
digitalWrite(input_pins[2], val3); //turn on.
delayMicroseconds(on_time); //for on_time.
digitalWrite(input_pins[0], LOW); //turn on.
digitalWrite(input_pins[1], LOW); //turn on.
digitalWrite(input_pins[2], LOW); //turn on.
delayMicroseconds(off_time); //for off_time.
}
}
void blink(int a_val, int b_val, int c_val, int time) {
digitalWrite(input_pins[0], a_val);
digitalWrite(input_pins[1], b_val);
digitalWrite(input_pins[2], c_val);
delay(time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delay(time);
}
// void turn_on(int a_val, int b_val, int c_val) {
// digitalWrite(input_pins[0], a_val);
// digitalWrite(input_pins[1], b_val);
// digitalWrite(input_pins[2], c_val);
// }
// void turn_off() {
// digitalWrite(input_pins[0], LOW);
// digitalWrite(input_pins[1], LOW);
// digitalWrite(input_pins[2], LOW);
// }circuit:

i’ll need to clean up the program to make it work for an array of leds (fade in, stay in, fade out). but i think i can do that.
there is one tiny problem: if i reset all the input pins to low, it powers up the 0th-pin. so, i think the downside is that i’ll just have to not use the zeroth pin. other than that, the pulsating works fine with the multiplexor across multiple leds.
i could use the inh pin, but that might complicate everything a little bit. i’ll take a call later — my brain is blanking out.
now, i’m going to start building my circuit, complete with the fsr; and work on the interactions.

i figured out the programmatic flow. every loop, all the leds will get brightness values. they can either be fading in, fading out, or be staying. actually, they will almost always pulsate (because the electrons are going at a very slow pace).
i then thought about different ways to make the led light up, and what i’d prefer.

then, i detailed out all the possible states; of both my program & the fsr.

made the leds light up in succession.
//interaction test.
/*
truth table for CD4051B:
[a,b,c] [high || low] = on_channel (source: datasheet)
[LOW, LOW, LOW] = 0;
[HIGH, LOW, LOW] = 1;
[LOW, HIGH, LOW] = 2;
[HIGH, HIGH, LOW] = 3
[LOW, LOW, HIGH] = 4;
[HIGH, LOW, HIGH] = 5;
[LOW, HIGH, HIGH] = 6;
[HIGH, HIGH, HIGH] = 7;
INHIBIT == HIGH = NONE;
*/
int input_pins[] = { 10, 11, 12 };
int inh_pin = 9;
int fsr_pin = A0;
int fsr_value = 0;
int fsr_prev_val = 0;
//helper to get array_size, when called during compile:
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//helper to get mux-values according to the truth table. chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3]; // persistent array for return
if (channel < 0 || channel > 7) {
vals[0] = vals[1] = vals[2] = LOW;
return vals;
}
// derive logic levels from the channel bits
vals[0] = (channel & 0b001) ? HIGH : LOW; // A
vals[1] = (channel & 0b010) ? HIGH : LOW; // B
vals[2] = (channel & 0b100) ? HIGH : LOW; // C
return vals;
}
void setup() {
//set pin-modes, and default value for inhibit.
int input_pins_length = get_array_length(input_pins);
for (int i = 0; i < input_pins_length; i++) {
pinMode(input_pins[i], OUTPUT);
digitalWrite(input_pins[i], LOW);
}
pinMode(fsr_pin, INPUT);
pinMode(inh_pin, OUTPUT);
digitalWrite(inh_pin, LOW);
}
void loop() {
light_up(1, 255, 30);
delay (100);
light_up(2, 255, 30);
delay (100);
light_up(3, 255, 30);
delay (100);
}
void light_up(int channel, int brightness, int interval) {
// brightness: 0–255
// interval: base PWM timing (microseconds per brightness step)
// hold duration is fixed internally
int* values = get_mux_values(channel);
const int max_brightness = 255;
const int hold_duration = 200; // <-- fixed hold time (ms)
digitalWrite(inh_pin, LOW); // enable MUX
// --- fade in ---
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
digitalWrite(input_pins[0], values[0]);
digitalWrite(input_pins[1], values[1]);
digitalWrite(input_pins[2], values[2]);
delayMicroseconds(on_time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delayMicroseconds(off_time);
}
// --- hold steady at chosen brightness ---
unsigned long start_time = millis();
while (millis() - start_time < hold_duration) {
int on_time = brightness * interval;
int off_time = (max_brightness * interval) - on_time;
digitalWrite(input_pins[0], values[0]);
digitalWrite(input_pins[1], values[1]);
digitalWrite(input_pins[2], values[2]);
delayMicroseconds(on_time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delayMicroseconds(off_time);
}
// --- fade out ---
for (int b = brightness; b >= 0; b--) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
digitalWrite(input_pins[0], values[0]);
digitalWrite(input_pins[1], values[1]);
digitalWrite(input_pins[2], values[2]);
delayMicroseconds(on_time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delayMicroseconds(off_time);
}
}
// void light_up(int channel, const int interval) {
// int* values = get_mux_values(channel);
// const int max_brightness = 255;
// //fade in the led:
// for (int b = 0; b <= max_brightness; b++) {
// int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
// int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
// //send pseudo-pwm signal:
// digitalWrite(input_pins[0], values[0]); //turn on.
// digitalWrite(input_pins[1], values[1]); //turn on.
// digitalWrite(input_pins[2], values[2]); //turn on.
// delayMicroseconds(on_time); //for on_time.
// digitalWrite(input_pins[0], LOW); //turn on.
// digitalWrite(input_pins[1], LOW); //turn on.
// digitalWrite(input_pins[2], LOW); //turn on.
// delayMicroseconds(off_time); //for off_time.
// }
// //stay on for a bit before going:
// digitalWrite(input_pins[0], values[0]); //turn on.
// digitalWrite(input_pins[1], values[1]); //turn on.
// digitalWrite(input_pins[2], values[2]); //turn on.
// delay(interval / 2);
// //fade out the led:
// for (int b = max_brightness; b >= 0; b--) {
// int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
// int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
// //send pseudo-pwm signal:
// digitalWrite(input_pins[0], values[0]); //turn on.
// digitalWrite(input_pins[1], values[1]); //turn on.
// digitalWrite(input_pins[2], values[2]); //turn on.
// delayMicroseconds(on_time); //for on_time.
// digitalWrite(input_pins[0], LOW); //turn on.
// digitalWrite(input_pins[1], LOW); //turn on.
// digitalWrite(input_pins[2], LOW); //turn on.
// delayMicroseconds(off_time); //for off_time.
// }
// }
void fade_signal(int val1, int val2, int val3, const int interval) {
digitalWrite(inh_pin, LOW);
const int max_brightness = 255;
//fade in the led:
for (int b = 0; b <= 255; b++) {
int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
//send pseudo-pwm signal:
digitalWrite(input_pins[0], val1); //turn on.
digitalWrite(input_pins[1], val2); //turn on.
digitalWrite(input_pins[2], val3); //turn on.
delayMicroseconds(on_time); //for on_time.
digitalWrite(input_pins[0], LOW); //turn on.
digitalWrite(input_pins[1], LOW); //turn on.
digitalWrite(input_pins[2], LOW); //turn on.
delayMicroseconds(off_time); //for off_time.
}
//when the above is completed, we fade out the led:
//fade out the led:
for (int b = 255; b >= 0; b--) {
int on_time = b * interval; //stay on at this brightness level for an interval of time. at 0 brightness, it won't be on at all.
int off_time = (max_brightness * interval) - on_time; //stay off for the remainder of the time.
//send pseudo-pwm signal:
digitalWrite(input_pins[0], val1); //turn on.
digitalWrite(input_pins[1], val2); //turn on.
digitalWrite(input_pins[2], val3); //turn on.
delayMicroseconds(on_time); //for on_time.
digitalWrite(input_pins[0], LOW); //turn on.
digitalWrite(input_pins[1], LOW); //turn on.
digitalWrite(input_pins[2], LOW); //turn on.
delayMicroseconds(off_time); //for off_time.
}
}
also read all technology is assistive, by sarah hendren.
put all stages as enums, and cleaned up the program.
//interaction test.
int input_pins[] = { 10, 11, 12 };
int inh_pin = 9;
int fsr_pin = A0;
int fsr_value = 0;
int fsr_prev_val = 0;
int fsr_noise = 20;
enum FSR_Stage {
LOW_TO_HIGH,
HIGH_TO_HIGH,
LOW_TO_LOW,
HIGH_TO_LOW
};
FSR_Stage current_fsr_stage = LOW_TO_LOW;
enum Circuit_Stage {
DISPLAYER,
POWERING,
POWERED,
DEPOWER,
END
};
Circuit_Stage current_circuit_stage = DISPLAYER;
//helper to get array_size, when called during compile:
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//helper to get mux-values according to the truth table. chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3]; // persistent array for return
if (channel < 0 || channel > 7) {
vals[0] = vals[1] = vals[2] = LOW;
return vals;
}
// derive logic levels from the channel bits
vals[0] = (channel & 0b001) ? HIGH : LOW; // A
vals[1] = (channel & 0b010) ? HIGH : LOW; // B
vals[2] = (channel & 0b100) ? HIGH : LOW; // C
return vals;
}
void setup() {
//set pin-modes, and default value for inhibit.
int input_pins_length = get_array_length(input_pins);
for (int i = 0; i < input_pins_length; i++) {
pinMode(input_pins[i], OUTPUT);
digitalWrite(input_pins[i], LOW);
}
pinMode(fsr_pin, INPUT);
pinMode(inh_pin, OUTPUT);
digitalWrite(inh_pin, LOW);
}
void loop() {
//a bunch of calls to light up.
fsr_value = analogRead(fsr_pin);
check_fsr_stage(fsr_value, fsr_prev_val);
change_circuit_stage();
// light_up(1, 255, 30);
// delay(100);
// light_up(2, 255, 30);
// delay(100);
// light_up(3, 255, 30);
// delay(100);
fsr_prev_val = fsr_value; //set previous fsr value to be fsr value.
}
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) {
// gone from LOW to HIGH.
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) {
// gone from HIGH to HIGH.
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) {
// gone from LOW to LOW.
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) {
// gone from HIGH to LOW.
current_fsr_stage = HIGH_TO_LOW;
}
}
void change_circuit_stage(){
if (current_fsr_stage==HIGH_TO_HIGH){
for (int i = 0; i<=3; i++){
light_up(i, 255, 10);
}
}
if (current_fsr_stage==LOW_TO_LOW){
for (int i = 0; i<=3; i++){
light_up(i, 10, 10);
}
}
}
void light_up(int channel, int brightness, int interval) {
// brightness: 0–255
// interval: base PWM timing (microseconds per brightness step)
// hold duration is fixed internally
int* values = get_mux_values(channel);
const int max_brightness = 255;
const int hold_duration = 100; // <-- fixed hold time (ms)
digitalWrite(inh_pin, LOW); // enable MUX
// --- fade in ---
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
digitalWrite(input_pins[0], values[0]);
digitalWrite(input_pins[1], values[1]);
digitalWrite(input_pins[2], values[2]);
delayMicroseconds(on_time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delayMicroseconds(off_time);
}
// --- hold steady at chosen brightness ---
unsigned long start_time = millis();
while (millis() - start_time < hold_duration) {
int on_time = brightness * interval;
int off_time = (max_brightness * interval) - on_time;
digitalWrite(input_pins[0], values[0]);
digitalWrite(input_pins[1], values[1]);
digitalWrite(input_pins[2], values[2]);
delayMicroseconds(on_time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delayMicroseconds(off_time);
}
// --- fade out ---
for (int b = brightness; b >= 0; b--) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
digitalWrite(input_pins[0], values[0]);
digitalWrite(input_pins[1], values[1]);
digitalWrite(input_pins[2], values[2]);
delayMicroseconds(on_time);
digitalWrite(input_pins[0], LOW);
digitalWrite(input_pins[1], LOW);
digitalWrite(input_pins[2], LOW);
delayMicroseconds(off_time);
}
}
so, now, i have stages and i have pwm through digital output via a multiplexor. now, i just have to put things together :)
because there are 36 leds, i will have to loop through all pins of each multiplexor, and then loop through how many multiplexors i have. this led me to think about multidimensional-arrays.
i decided to program from scratch, and borrow from my past work wherever possible.
// multiple muxes; arjun, october 23rd.
//we have 2 muxes, each with 3 control pins. somehow, i need to define the array as one size greater than its length.
int mux[2][3] = {
{ 10, 11, 12 },
{ 7, 8, 9 }
};
//helper to get array size.
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//helper to get mux-values according to the truth table. chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
void setup() {
Serial.begin(9600); //start serial communication.
//set pin modes for all mux control pins
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[i]); j++) {
pinMode(mux[i][j], OUTPUT);
}
}
}
void loop() {
int num_muxes = get_array_length(mux);
int num_channels = 3; // how many channels to loop through per mux
for (int m = 0; m <= num_muxes; m++) {
for (int ch = 0; ch < num_channels; ch++) {
light_up(mux[m], ch, 255, 10);
delay(100);
}
}
}
//function to light up leds.
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
const int hold_duration = 100;
//fade in:
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//hold:
unsigned long start_time = millis();
while (millis() - start_time < hold_duration) {
int on_time = brightness * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//fade out:
for (int b = brightness; b >= 0; b--) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
}i then spoke with octavio about how to solder this, and jasmine gave me led-tubes to test.

tomorrow, i’ll go and buy my materials; begin soldering; fabricate; and get this to work. i do have a little bit of interaction to fine-tune, and this would be better with 36 pnp transistors (so that the leds stay on), but i’ll keep it to basic for now.
i ran into a problem where the arduino refuses to let out of a loop, until it has completed the loop.
for example, i check all the states of my fsr. if my state is a certain way, say HIGH_TO_HIGH, i’m running through all the channels of the multiplexor and, also, through all the multiplexors and sending them pseudo-pwms extremely quickly.
however, if i let go during the time that the arduino is running the loop, the light up sequence still runs.
code:
// multiple muxes; arjun, october 23rd.
//helper to get array size.
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//mux stuff:
//we have 2 muxes, each with 3 control pins. somehow, i need to define the array as one size greater than its length.
int mux[2][3] = {
{ 10, 11, 12 },
{ 7, 8, 9 }
};
//helper to get mux-values according to the truth table. chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 4;
//fsr variables:
int fsr_pin = A0;
//variables to later store current & previous valuies.
int fsr_value = 0;
int fsr_prev_val = 0;
//a number to account for electrical-noise that the fsr will experience.
int fsr_noise = 20;
//my circuit & fsr have stages. i store them as enums, to make them more readable.
enum FSR_Stage {
LOW_TO_HIGH, //the fsr has been pressed.
HIGH_TO_HIGH, //the fsr is remaining pressed.
LOW_TO_LOW, //the fsr hasn't been pressed.
HIGH_TO_LOW //the fsr has been released from a press.
};
FSR_Stage current_fsr_stage = LOW_TO_LOW; //declare a variable to keep changing as the program goes on.
enum Circuit_Stage {
DISPLAYER, //when a circuit is not being interacted with.
POWERING, //when a circuit is being interacted with.
POWERED, //when a circuit waits to be released from interactivity.
DEPOWER, //when a circuit is released.
END //a little something, before it goes back to displayer.
};
Circuit_Stage current_circuit_stage = DISPLAYER; //declare a variable to keep changing as the program goes on.
void setup() {
Serial.begin(9600); //start serial communication.
//set pin modes for all mux control pins:
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[i]); j++) {
pinMode(mux[i][j], OUTPUT);
}
}
//set pin mode for fsr:
pinMode(fsr_pin, INPUT);
pinMode(2, OUTPUT);
}
void loop() {
fsr_value = analogRead(fsr_pin); //get current value.
check_fsr_stage(fsr_value, fsr_prev_val);
change_circuit_stage();
// int num_muxes = get_array_length(mux);
// int num_channels = 3; // how many channels to loop through per mux
// for (int m = 0; m <= num_muxes; m++) {
// for (int ch = 0; ch < num_channels; ch++) {
// light_up(mux[m], ch, 255, 10);
// delay(100);
// }
// }
fsr_prev_val = fsr_value; //whatever was current value, store as previous value (because current value will be changed next, so you have something to compare against.)
}
//function to check & assign fsr-stage based on current & previous value.
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) {
// gone from LOW to HIGH.
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) {
// gone from HIGH to HIGH.
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) {
// gone from LOW to LOW.
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) {
// gone from HIGH to LOW.
current_fsr_stage = HIGH_TO_LOW;
}
}
void change_circuit_stage() {
if (current_fsr_stage == LOW_TO_LOW) {
//this hasn't changed. for now, i'm going to make an led blink.
displayer();
} else if (current_fsr_stage == LOW_TO_HIGH) {
//send the first electron to led 0.
only_first_led();
} else if (current_fsr_stage == HIGH_TO_HIGH) {
//the button is pressed. send more electrons, from the 2nd led to the power strip.
power_up();
}
}
void displayer() {
digitalWrite(2, HIGH);
delay(100);
digitalWrite(2, LOW);
delay(100);
}
void only_first_led() {
digitalWrite(mux[0][0], HIGH);
digitalWrite(mux[0][1], LOW);
digitalWrite(mux[0][2], LOW);
}
void power_up() {
const int brightness = 255;
const int interval = 10;
for (int m = 0; m < num_muxes; m++) { // loop through muxes
for (int ch = 0; ch < channels; ch++) { // loop through channels
light_up(mux[m], ch, brightness, interval);
delay(100); // brief pause between channels (optional)
}
}
}
//function to fade in leds, let them be lit, and fade them out; simulating an electron being passed in time.
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
const int hold_duration = 100;
//fade in:
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//hold:
unsigned long start_time = millis();
while (millis() - start_time < hold_duration) {
int on_time = brightness * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//fade out:
for (int b = brightness; b >= 0; b--) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
}learnt about switch from the arduino library.
i kind of hit a wall. i became desperate, and tried to use chatgpt. it gave me an unexpected result:
it sent a pulse really quickly through the circuit. actually, this movement felt closer to electrons — i realised that the pace of electrons moving (and colliding) is what generates the heat (while talking to gabriel). this shows that better than my slow led thing.
so, if you didn’t guess it already, i started from scratch again. so, i made & made — and tested again. and became more & more desperate.
but then i realised that i was chasing the wrong thing. again, as per my approach to itp, i am a student; not a maker (anymore).
i stopped at this piece of code, and went back to where i last knew what was happening.
// multiple muxes; arjun, october 23rd.
//helper to get array size.
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//mux stuff:
int mux[2][3] = {
{ 10, 11, 12 },
{ 7, 8, 9 }
};
//helper to get mux-values according to the truth table.
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 4;
//fsr variables:
int fsr_pin = A0;
int fsr_value = 0;
int fsr_prev_val = 0;
int fsr_noise = 20;
//circuit & FSR stages
enum FSR_Stage {
LOW_TO_HIGH,
HIGH_TO_HIGH,
LOW_TO_LOW,
HIGH_TO_LOW
};
FSR_Stage current_fsr_stage = LOW_TO_LOW;
enum Circuit_Stage {
DISPLAYER,
POWERING,
POWERED,
DEPOWER,
END
};
Circuit_Stage current_circuit_stage = DISPLAYER;
//non-blocking fade state
struct FadeState {
int step = 0; // current fade step
int mux_index = 0; // current mux
int channel_index = 0; // current channel
bool fading_in = true; // fade direction
unsigned long last_time = 0; // last update timestamp
};
FadeState fade;
//fade settings
const int max_brightness = 200;
const int interval = 1; // step interval in ms
const unsigned long pause_between_channels = 1; // ms
const unsigned long hold_duration = 1; // ms
void setup() {
Serial.begin(9600);
for (int i = 0; i < get_array_length(mux); i++)
for (int j = 0; j < get_array_length(mux[0]); j++)
pinMode(mux[i][j], OUTPUT);
pinMode(fsr_pin, INPUT);
pinMode(2, OUTPUT);
}
void loop() {
fsr_value = analogRead(fsr_pin);
check_fsr_stage(fsr_value, fsr_prev_val);
// switch circuit stage
switch (current_fsr_stage) {
case LOW_TO_LOW: current_circuit_stage = DISPLAYER; break;
case LOW_TO_HIGH: break;
case HIGH_TO_HIGH: current_circuit_stage = POWERING; break;
case HIGH_TO_LOW: current_circuit_stage = DEPOWER; break;
}
// act based on circuit stage
switch (current_circuit_stage) {
case DISPLAYER: displayer(); break;
case POWERING: power_up(); break;
case POWERED: break;
case DEPOWER: reset_sequence(); break;
case END: break;
}
fsr_prev_val = fsr_value;
// update non-blocking fade for POWERING
update_fade();
}
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) current_fsr_stage = LOW_TO_HIGH;
else if (last_fsr > fsr_noise && current_fsr > fsr_noise) current_fsr_stage = HIGH_TO_HIGH;
else if (last_fsr < fsr_noise && current_fsr < fsr_noise) current_fsr_stage = LOW_TO_LOW;
else if (last_fsr > fsr_noise && current_fsr < fsr_noise) current_fsr_stage = HIGH_TO_LOW;
}
// turn off all mux pins and reset fade
void reset_sequence() {
for (int m = 0; m < num_muxes; m++)
for (int i = 0; i < 3; i++)
digitalWrite(mux[m][i], LOW);
fade.step = 0;
fade.mux_index = 0;
fade.channel_index = 0;
fade.fading_in = true;
fade.last_time = millis();
}
void displayer() {
digitalWrite(2, HIGH);
delay(100);
digitalWrite(2, LOW);
delay(100);
}
// handle powering sequence: keeps fade state responsive
void power_up() {
if (current_fsr_stage != HIGH_TO_HIGH) {
fade.step = 0;
fade.mux_index = 0;
fade.channel_index = 0;
fade.fading_in = true;
}
}
// non-blocking fade update
void update_fade() {
if (current_circuit_stage != POWERING) return;
unsigned long now = millis();
if (now - fade.last_time < interval) return;
fade.last_time = now;
int* values = get_mux_values(fade.channel_index);
// software PWM: simulate fade by controlling on/off duration
int on_time = fade.step;
int off_time = max_brightness - fade.step;
for (int i = 0; i < 3; i++) digitalWrite(mux[fade.mux_index][i], values[i]);
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) digitalWrite(mux[fade.mux_index][i], LOW);
delayMicroseconds(off_time);
// advance step
if (fade.fading_in) {
fade.step++;
if (fade.step >= max_brightness) fade.fading_in = false;
} else {
fade.step--;
if (fade.step <= 0) {
fade.step = 0;
fade.fading_in = true;
fade.channel_index++;
if (fade.channel_index >= channels) {
fade.channel_index = 0;
fade.mux_index++;
}
if (fade.mux_index >= num_muxes) {
fade.mux_index = 0;
fade.channel_index = 0;
current_circuit_stage = POWERED;
}
}
}
}
i worked on this for so long. decided to leave this for a bit & go to itp & friends instead.
251025_2000:
the first thing i did was to go back to the piece of code that i could understand.
// multiple muxes; arjun, october 23rd.
//helper to get array size.
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//mux stuff:
//we have 2 muxes, each with 3 control pins. somehow, i need to define the array as one size greater than its length.
int mux[2][3] = {
{ 10, 11, 12 },
{ 7, 8, 9 }
};
//helper to get mux-values according to the truth table. chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 4;
//fsr variables:
int fsr_pin = A0;
//variables to later store current & previous valuies.
int fsr_value = 0;
int fsr_prev_val = 0;
//a number to account for electrical-noise that the fsr will experience.
int fsr_noise = 20;
//my circuit & fsr have stages. i store them as enums, to make them more readable.
enum FSR_Stage {
LOW_TO_HIGH, //the fsr has been pressed.
HIGH_TO_HIGH, //the fsr is remaining pressed.
LOW_TO_LOW, //the fsr hasn't been pressed.
HIGH_TO_LOW //the fsr has been released from a press.
};
FSR_Stage current_fsr_stage = LOW_TO_LOW; //declare a variable to keep changing as the program goes on.
enum Circuit_Stage {
DISPLAYER, //when a circuit is not being interacted with.
POWERING, //when a circuit is being interacted with.
POWERED, //when a circuit waits to be released from interactivity.
DEPOWER, //when a circuit is released.
END //a little something, before it goes back to displayer.
};
Circuit_Stage current_circuit_stage = DISPLAYER; //declare a variable to keep changing as the program goes on.
void setup() {
Serial.begin(9600); //start serial communication.
//set pin modes for all mux control pins:
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[0]); j++) { //since sub-array lengths are the same for everything.
pinMode(mux[i][j], OUTPUT);
}
}
//set pin mode for fsr:
pinMode(fsr_pin, INPUT);
pinMode(2, OUTPUT);
}
void loop() {
fsr_value = analogRead(fsr_pin); //get current value.
check_fsr_stage(fsr_value, fsr_prev_val); //always check fsr value, and change states.
//use a switch to change circuit stage:
switch (current_fsr_stage) {
case LOW_TO_LOW:
current_circuit_stage = DISPLAYER;
break;
case LOW_TO_HIGH:
//button has just been pressed.
break;
case HIGH_TO_HIGH:
current_circuit_stage = POWERING;
break;
case HIGH_TO_LOW:
current_circuit_stage = DEPOWER;
break;
}
switch (current_circuit_stage) {
case DISPLAYER:
displayer();
break;
case POWERING:
power_up();
break;
case POWERED:
break;
case DEPOWER:
break;
case END:
break;
}
fsr_prev_val = fsr_value; //assign current value to variable storing previous value, since current value will change in the next loop-run.
}
//function to check & assign fsr-stage based on current & previous value.
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) {
// gone from LOW to HIGH.
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) {
// gone from HIGH to HIGH.
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) {
// gone from LOW to LOW.
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) {
// gone from HIGH to LOW.
current_fsr_stage = HIGH_TO_LOW;
}
}
//function to power up the circuit.
void power_up() {
for (int i = 0; i < num_muxes; i++) {
for (int ch = 0; ch < channels; ch++) {
light_up(mux[i], ch, 255, 0.25);
delay(25);
}
}
}
//function to show things when the circuit is not being touched.
void displayer() {
digitalWrite(2, HIGH);
delay(100);
digitalWrite(2, LOW);
delay(100);
}
//function to fade in leds, let them be lit, and fade them out; simulating an electron being passed in time.
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
//fade in:
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//fade out:
for (int b = brightness; b >= 0; b--) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
}
i modified it to get the effect i wanted.
alanna once said, in passing (and with humour), that i was (electronically) making many christmas-lights. it pissed me off then. but, yes, i did make christmas lights. i do think, however, that in context it can be perceived as more than just christmas lights.
i then moved on to removing delay. my experiments with chat-gpt showed me that it used millis() to make the functions non-blocking. i also read up about whether this was true.
then, i prompted chat-gpt with this:
great. let’s move on to the next thing.
in my previous experiments with you, you evaluated blocks of my code as ‘blocking’, because i was using delay() or delayMicroseconds(). i want to know how i may make my code non-blocking, so that it remains reactive to input / internal state changes.
i don’t want you to write my code — i want you to help me understand & get there on my own.
it was kind in response:

i then watched this video: https://www.youtube.com/watch?v=kSghXUExrIE
learnt that millis() tracks milliseconds that have elapsed since the arduino was powered on. it is an unsigned long, and wraps back to 0 after 4,294,967,295 milliseconds (about 49.7 days). so, one can keep track of the time the board was powered on, then use a variable to assign a timestamp when a function was called, and if the current_time-stamp - the function_called_timestamp >= delay, you can repeat the same operation.
unsigned long function_time = 0;
void loop{
unsigned long current_time = millis();
if (current_time-function_time >= required_delay){
//do the thing.
//reset timestamp of the thing being done:
function_time = current_time;
}
}
i felt like i had a solid grasp on the programming, even without solving delay() vs millis(). so, i moved on to fabrication.
first, i went and saw light-diffusion through each material available at blics. i made a mistake, and should’ve done this before the weekend (so that i had access to canal plastics).
then, i measured everything out — how far did the leds need to be, how big the whole thing would be; et-cetera.

nikolai helped me think through how i could have a flat, solid base; with wood.

i then laser-cut holes for the leds on a small piece of test-basswood. it worked perfectly.

i then printed everything out, measured it and laser cut.


code dumps:
last working code; 251028_0441:
// multiple muxes; arjun, october 23rd.
//helper to get array size.
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//mux stuff:
//we have 5 muxes, each with 3 control pins. somehow, i need to define the array as one size greater than its length.
int mux[5][3] = {
{ 12, 11, 10 },
{ 9, 8, 7 },
{ 6, 5, 4 },
{ 3, 2, 13 },
{ 14, 15, 16 }
};
//helper to get mux-values according to the truth table.
//chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 8;
//fsr variables:
int fsr_pin = A0;
//variables to later store current & previous valuies.
int fsr_value = 0;
int fsr_prev_val = 0;
//a number to account for electrical-noise that the fsr will experience.
int fsr_noise = 20;
//my circuit & fsr have stages. i store them as enums, to make them more readable.
enum FSR_Stage {
LOW_TO_HIGH, //the fsr has been pressed.
HIGH_TO_HIGH, //the fsr is remaining pressed.
LOW_TO_LOW, //the fsr hasn't been pressed.
HIGH_TO_LOW //the fsr has been released from a press.
};
FSR_Stage current_fsr_stage = LOW_TO_LOW;
//declare a variable to keep changing as the program goes on.
enum Circuit_Stage {
DISPLAYER, //when a circuit is not being interacted with.
POWERING, //when a circuit is being interacted with.
POWERED, //when a circuit waits to be released from interactivity.
DEPOWER, //when a circuit is released.
END //a little something, before it goes back to displayer.
};
Circuit_Stage current_circuit_stage = DISPLAYER;
//declare a variable to keep changing as the program goes on.
void setup() {
Serial.begin(9600); //start serial communication.
//set pin modes for all mux control pins:
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[0]); j++) { //since sub-array lengths are the same for everything.
pinMode(mux[i][j], OUTPUT);
}
}
//set pin mode for fsr:
pinMode(fsr_pin, INPUT);
pinMode(2, OUTPUT);
}
void loop() {
power_up();
delay(100);
// fsr_value = analogRead(fsr_pin); //get current value.
// check_fsr_stage(fsr_value, fsr_prev_val); //always check fsr value, and change states.
// //use a switch to change circuit stage:
// switch (current_fsr_stage) {
// case LOW_TO_LOW:
// current_circuit_stage = DISPLAYER;
// break;
// case LOW_TO_HIGH:
// //button has just been pressed.
// break;
// case HIGH_TO_HIGH:
// current_circuit_stage = POWERING;
// break;
// case HIGH_TO_LOW:
// current_circuit_stage = DEPOWER;
// break;
// }
// switch (current_circuit_stage) {
// case DISPLAYER:
// displayer();
// break;
// case POWERING:
// power_up();
// break;
// case POWERED:
// break;
// case DEPOWER:
// depower();
// break;
// case END:
// break;
// }
// fsr_prev_val = fsr_value; //assign current value to variable storing previous value, since current value will change in the next loop-run.
}
//function to check & assign fsr-stage based on current & previous value.
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) { // gone from LOW to HIGH.
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) { // gone from HIGH to HIGH.
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) { // gone from LOW to LOW.
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) { // gone from HIGH to LOW.
current_fsr_stage = HIGH_TO_LOW;
}
}
//function to show things when the circuit is not being touched.
void displayer() {
digitalWrite(2, HIGH);
delay(100);
digitalWrite(2, LOW);
delay(100);
}
//function to power up the circuit.
unsigned long light_up_pass_ms = 0;
void power_up() {
for (int i = 0; i < num_muxes; i++) {
for (int ch = 0; ch < channels; ch++) {
light_up(mux[i], ch, 255, 1);
}
}
}
//function to fade in leds, let them be lit, and fade them out; simulating an electron being passed in time.
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
//fade in:
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//fade out:
for (int b = brightness; b >= 0; b--) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
}
//function to show things when the circuit is not being touched.
void depower() {
digitalWrite(2, HIGH);
delay(1000);
digitalWrite(2, LOW);
delay(1000);
}
last working code:
// multiple muxes; arjun, october 23rd.
//helper to get array size.
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//mux stuff:
//we have 5 muxes, each with 3 control pins. somehow, i need to define the array as one size greater than its length.
int mux[5][3] = {
{ 12, 11, 10 },
{ 9, 8, 7 },
{ 6, 5, 4 },
{ 3, 2, 13 },
{ 14, 15, 16 }
};
//helper to get mux-values according to the truth table.
//chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 8;
//fsr variables:
int fsr_pin = A3;
//variables to later store current & previous valuies.
int fsr_value = 0;
int fsr_prev_val = 0;
//a number to account for electrical-noise that the fsr will experience.
int fsr_noise = 5;
//my circuit & fsr have stages. i store them as enums, to make them more readable.
enum FSR_Stage {
LOW_TO_HIGH, //the fsr has been pressed.
HIGH_TO_HIGH, //the fsr is remaining pressed.
LOW_TO_LOW, //the fsr hasn't been pressed.
HIGH_TO_LOW //the fsr has been released from a press.
};
FSR_Stage current_fsr_stage = LOW_TO_LOW;
//declare a variable to keep changing as the program goes on.
enum Circuit_Stage {
DISPLAYER, //when a circuit is not being interacted with.
POWERING, //when a circuit is being interacted with.
POWERED, //when a circuit waits to be released from interactivity.
DEPOWER, //when a circuit is released.
END //a little something, before it goes back to displayer.
};
Circuit_Stage current_circuit_stage = DISPLAYER;
//final led:
int final_led = 18;
//declare a variable to keep changing as the program goes on.
void setup() {
Serial.begin(9600); //start serial communication.
//set pin modes for all mux control pins:
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[0]); j++) { //since sub-array lengths are the same for everything.
pinMode(mux[i][j], OUTPUT);
}
}
//set pin mode for fsr:
pinMode(fsr_pin, INPUT);
pinMode(final_led, OUTPUT);
}
void loop() {
fsr_value = analogRead(fsr_pin); //get current value.
Serial.println(fsr_value);
check_fsr_stage(fsr_value, fsr_prev_val); //always check fsr value, and change states.
//use a switch to change circuit stage:
switch (current_fsr_stage) {
case LOW_TO_LOW:
current_circuit_stage = DISPLAYER;
break;
case LOW_TO_HIGH:
//button has just been pressed.
break;
case HIGH_TO_HIGH:
current_circuit_stage = POWERING;
break;
case HIGH_TO_LOW:
current_circuit_stage = DEPOWER;
break;
}
switch (current_circuit_stage) {
case DISPLAYER:
// displayer();
break;
case POWERING:
power_up();
break;
case POWERED:
break;
case DEPOWER:
depower();
break;
case END:
break;
}
fsr_prev_val = fsr_value; //assign current value to variable storing previous value, since current value will change in the next loop-run.
}
//function to check & assign fsr-stage based on current & previous value.
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) { // gone from LOW to HIGH.
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) { // gone from HIGH to HIGH.
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) { // gone from LOW to LOW.
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) { // gone from HIGH to LOW.
current_fsr_stage = HIGH_TO_LOW;
}
}
//function to show things when the circuit is not being touched.
void displayer() {
digitalWrite(2, HIGH);
delay(100);
digitalWrite(2, LOW);
delay(100);
}
//function to power up the circuit.
unsigned long last_step_time = 0;
int current_mux = 0;
int current_channel = 0;
int fade = 0;
int fade_direction = 1; // 1 = up, -1 = down
// timing constants (easy to tweak)
const int fade_step_delay = 2; // ms between brightness changes
const int channel_gap = 30; // ms gap between channel changes
const int max_brightness = 255;
void power_up() {
unsigned long now = millis();
// handle fade timing
if (now - last_step_time >= fade_step_delay) {
fade += fade_direction * 10; // change per step
if (fade >= max_brightness) {
fade = max_brightness;
fade_direction = -1;
} else if (fade <= 0) {
fade = 0;
fade_direction = 1;
// advance to next channel
current_channel++;
if (current_channel >= channels) {
current_channel = 0;
current_mux++;
if (current_mux >= num_muxes) {
current_mux = 0; // loop back to start
}
}
// little perceptible gap between LEDs
last_step_time = now + channel_gap;
return;
}
light_up(mux[current_mux], current_channel, fade);
last_step_time = now;
}
}
//function to set current LED brightness (PWM simulated)
void light_up(int input_pins[3], int channel, int brightness) {
int* values = get_mux_values(channel);
//simple pseudo-PWM: brightness controls pulse width
int on_time = brightness * 4; // microseconds
int off_time = (255 - brightness) * 4;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//function to show things when the circuit is not being touched.
void depower() {
digitalWrite(2, HIGH);
delay(1000);
digitalWrite(2, LOW);
delay(1000);
}
last working code:
// multiple muxes; arjun, october 23rd.
//helper to get array size.
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
//mux stuff:
//we have 5 muxes, each with 3 control pins. somehow, i need to define the array as one size greater than its length.
int mux[5][3] = {
{ 12, 11, 10 },
{ 9, 8, 7 },
{ 6, 5, 4 },
{ 3, 2, 13 },
{ 14, 15, 16 }
};
//helper to get mux-values according to the truth table.
//chat-gpt found some obscure logic that connects all pin-numbers with simple if-conditions.
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 8;
//fsr variables:
int fsr_pin = A0;
//variables to later store current & previous valuies.
int fsr_value = 0;
int fsr_prev_val = 0;
//a number to account for electrical-noise that the fsr will experience.
int fsr_noise = 20;
//my circuit & fsr have stages. i store them as enums, to make them more readable.
enum FSR_Stage {
LOW_TO_HIGH, //the fsr has been pressed.
HIGH_TO_HIGH, //the fsr is remaining pressed.
LOW_TO_LOW, //the fsr hasn't been pressed.
HIGH_TO_LOW //the fsr has been released from a press.
};
FSR_Stage current_fsr_stage = LOW_TO_LOW;
//declare a variable to keep changing as the program goes on.
enum Circuit_Stage {
DISPLAYER, //when a circuit is not being interacted with.
POWERING, //when a circuit is being interacted with.
POWERED, //when a circuit waits to be released from interactivity.
DEPOWER, //when a circuit is released.
END //a little something, before it goes back to displayer.
};
Circuit_Stage current_circuit_stage = DISPLAYER;
//declare a variable to keep changing as the program goes on.
void setup() {
Serial.begin(9600); //start serial communication.
//set pin modes for all mux control pins:
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[0]); j++) { //since sub-array lengths are the same for everything.
pinMode(mux[i][j], OUTPUT);
}
}
//set pin mode for fsr:
pinMode(fsr_pin, INPUT);
pinMode(2, OUTPUT);
}
void loop() {
fsr_value = analogRead(fsr_pin); //get current value.
check_fsr_stage(fsr_value, fsr_prev_val); //always check fsr value, and change states.
Serial.println(fsr_value);
//use a switch to change circuit stage:
switch (current_fsr_stage) {
case LOW_TO_LOW:
current_circuit_stage = DISPLAYER;
break;
case LOW_TO_HIGH:
//button has just been pressed.
break;
case HIGH_TO_HIGH:
current_circuit_stage = POWERING;
break;
case HIGH_TO_LOW:
current_circuit_stage = DEPOWER;
break;
}
switch (current_circuit_stage) {
case DISPLAYER:
break;
case POWERING:
power_up();
break;
case POWERED:
break;
case DEPOWER:
depower();
break;
case END:
break;
}
fsr_prev_val = fsr_value; //assign current value to variable storing previous value, since current value will change in the next loop-run.
}
//function to check & assign fsr-stage based on current & previous value.
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) { // gone from LOW to HIGH.
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) { // gone from HIGH to HIGH.
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) { // gone from LOW to LOW.
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) { // gone from HIGH to LOW.
current_fsr_stage = HIGH_TO_LOW;
}
}
//function to show things when the circuit is not being touched.
void displayer() {
digitalWrite(2, HIGH);
delay(100);
digitalWrite(2, LOW);
delay(100);
}
//function to power up the circuit.
unsigned long light_up_pass_ms = 0;
void power_up() {
for (int i = 0; i < num_muxes; i++) {
for (int ch = 0; ch < channels; ch++) {
light_up(mux[i], ch, 255, 1);
}
}
}
//function to fade in leds, let them be lit, and fade them out; simulating an electron being passed in time.
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
//fade in:
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
//fade out:
for (int b = brightness; b >= 0; b--) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], values[i]);
}
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) {
digitalWrite(input_pins[i], LOW);
}
delayMicroseconds(off_time);
}
}
//function to show things when the circuit is not being touched.
void depower() {
digitalWrite(2, HIGH);
delay(1000);
digitalWrite(2, LOW);
delay(1000);
}last working:
// multiple muxes; arjun, october 28th — with ghost fix + looping depower
// ------------------------------------------------------------
// helper to get array size
// ------------------------------------------------------------
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
// ------------------------------------------------------------
// mux setup
// ------------------------------------------------------------
int mux[5][3] = {
{ 12, 11, 10 },
{ 9, 8, 7 },
{ 6, 5, 4 },
{ 3, 2, 13 },
{ 14, 15, 16 }
};
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 8;
// ------------------------------------------------------------
// fsr + led setup
// ------------------------------------------------------------
int fsr_pin = A0;
int fsr_value = 0;
int fsr_prev_val = 0;
int fsr_noise = 20;
int final_led = 18;
// ------------------------------------------------------------
// enums for state tracking
// ------------------------------------------------------------
enum FSR_Stage { LOW_TO_HIGH,
HIGH_TO_HIGH,
LOW_TO_LOW,
HIGH_TO_LOW };
FSR_Stage current_fsr_stage = LOW_TO_LOW;
enum Circuit_Stage { DISPLAYER,
POWERING,
POWERED,
DEPOWER,
END };
Circuit_Stage current_circuit_stage = DISPLAYER;
// ------------------------------------------------------------
// setup
// ------------------------------------------------------------
void setup() {
Serial.begin(9600);
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[0]); j++) {
pinMode(mux[i][j], OUTPUT);
}
}
pinMode(fsr_pin, INPUT);
pinMode(final_led, OUTPUT);
digitalWrite(final_led, LOW);
}
// ------------------------------------------------------------
// loop
// ------------------------------------------------------------
void loop() {
fsr_value = analogRead(fsr_pin);
check_fsr_stage(fsr_value, fsr_prev_val);
Serial.println(fsr_value);
switch (current_circuit_stage) {
case DISPLAYER:
if (current_fsr_stage == LOW_TO_HIGH) {
current_circuit_stage = POWERING;
}
break;
case POWERING:
power_up(); // runs continuously while pressed
if (current_fsr_stage == HIGH_TO_LOW) {
current_circuit_stage = DEPOWER;
}
break;
case DEPOWER:
depower(); // runs once on release
current_circuit_stage = END;
break;
case END:
digitalWrite(final_led, LOW);
if (current_fsr_stage == LOW_TO_HIGH) {
current_circuit_stage = POWERING; // restart cycle
}
break;
}
fsr_prev_val = fsr_value;
}
// ------------------------------------------------------------
// helper: detect fsr transitions
// ------------------------------------------------------------
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) {
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) {
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) {
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) {
current_fsr_stage = HIGH_TO_LOW;
}
}
// ------------------------------------------------------------
// clear all mux pins (prevents ghosting)
// ------------------------------------------------------------
void clear_muxes() {
for (int i = 0; i < num_muxes; i++) {
for (int j = 0; j < 3; j++) {
digitalWrite(mux[i][j], LOW);
}
}
}
// ------------------------------------------------------------
// light up one LED (fade pulse style)
// ------------------------------------------------------------
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
// simple pulse
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) digitalWrite(input_pins[i], values[i]);
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) digitalWrite(input_pins[i], LOW);
delayMicroseconds(off_time);
}
// clear + settle to prevent ghost voltage
clear_muxes();
delayMicroseconds(200);
}
// ------------------------------------------------------------
// POWER UP: leds 1 → 31, continuous loop
// ------------------------------------------------------------
void power_up() {
for (int i = 0; i < num_muxes; i++) {
int ch_limit = (i < 4) ? 7 : 3; // last mux only has 3 channels
for (int ch = 1; ch <= ch_limit; ch++) {
light_up(mux[i], ch, 255, 1);
}
}
digitalWrite(final_led, HIGH);
}
// ------------------------------------------------------------
// DEPOWER: leds 8 → 24, loops 3x, same visual speed
// ------------------------------------------------------------
void depower() {
const int start_led = 8;
const int end_led = 24;
const int leds_per_mux = 7;
const int start_channel = 1;
const int num_leds = (end_led - start_led) + 1;
const int per_led_delay = 80; // ms per LED
digitalWrite(final_led, HIGH); // capacitor still charged
for (int cycle = 0; cycle < 3; cycle++) { // repeat 3x for clarity
for (int step = 0; step < num_leds; step++) {
int absolute_led = start_led + step;
int mux_index = (absolute_led - 1) / leds_per_mux;
int channel_index = ((absolute_led - 1) % leds_per_mux) + start_channel;
light_up(mux[mux_index], channel_index, 255, 1);
delay(per_led_delay);
}
}
digitalWrite(final_led, LOW); // fully discharged
}
working:
// multiple muxes; arjun, october 28th — ghost fix + smoother flow + #30 blink fix
// ------------------------------------------------------------
// helper to get array size
// ------------------------------------------------------------
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
// ------------------------------------------------------------
// mux setup
// ------------------------------------------------------------
int mux[5][3] = {
{ 12, 11, 10 },
{ 9, 8, 7 },
{ 6, 5, 4 },
{ 3, 2, 13 },
{ 14, 15, 16 }
};
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 8;
// ------------------------------------------------------------
// fsr + led setup
// ------------------------------------------------------------
int fsr_pin = A0;
int fsr_value = 0;
int fsr_prev_val = 0;
int fsr_noise = 20;
int final_led = 18;
// ------------------------------------------------------------
// enums for state tracking
// ------------------------------------------------------------
enum FSR_Stage { LOW_TO_HIGH,
HIGH_TO_HIGH,
LOW_TO_LOW,
HIGH_TO_LOW };
FSR_Stage current_fsr_stage = LOW_TO_LOW;
enum Circuit_Stage { DISPLAYER,
POWERING,
POWERED,
DEPOWER,
END };
Circuit_Stage current_circuit_stage = DISPLAYER;
// ------------------------------------------------------------
// setup
// ------------------------------------------------------------
void setup() {
Serial.begin(9600);
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[0]); j++) {
pinMode(mux[i][j], OUTPUT);
}
}
pinMode(fsr_pin, INPUT);
pinMode(final_led, OUTPUT);
digitalWrite(final_led, LOW);
}
// ------------------------------------------------------------
// loop
// ------------------------------------------------------------
void loop() {
fsr_value = analogRead(fsr_pin);
check_fsr_stage(fsr_value, fsr_prev_val);
Serial.println(fsr_value);
switch (current_circuit_stage) {
case DISPLAYER:
if (current_fsr_stage == LOW_TO_HIGH) {
current_circuit_stage = POWERING;
}
break;
case POWERING:
power_up(); // runs continuously while pressed
if (current_fsr_stage == HIGH_TO_LOW) {
current_circuit_stage = DEPOWER;
}
break;
case DEPOWER:
depower(); // runs once on release
current_circuit_stage = END;
break;
case END:
digitalWrite(final_led, LOW);
if (current_fsr_stage == LOW_TO_HIGH) {
current_circuit_stage = POWERING; // restart cycle
}
break;
}
fsr_prev_val = fsr_value;
}
// ------------------------------------------------------------
// helper: detect fsr transitions
// ------------------------------------------------------------
void check_fsr_stage(int current_fsr, int last_fsr) {
if (last_fsr < fsr_noise && current_fsr > fsr_noise) {
current_fsr_stage = LOW_TO_HIGH;
} else if (last_fsr > fsr_noise && current_fsr > fsr_noise) {
current_fsr_stage = HIGH_TO_HIGH;
} else if (last_fsr < fsr_noise && current_fsr < fsr_noise) {
current_fsr_stage = LOW_TO_LOW;
} else if (last_fsr > fsr_noise && current_fsr < fsr_noise) {
current_fsr_stage = HIGH_TO_LOW;
}
}
// ------------------------------------------------------------
// clear all mux pins (prevents ghosting)
// ------------------------------------------------------------
void clear_muxes() {
for (int i = 0; i < num_muxes; i++) {
for (int j = 0; j < 3; j++) {
digitalWrite(mux[i][j], LOW);
}
}
}
// ------------------------------------------------------------
// light up one LED (fade pulse style)
// ------------------------------------------------------------
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
// simple pulse
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) digitalWrite(input_pins[i], values[i]);
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) digitalWrite(input_pins[i], LOW);
delayMicroseconds(off_time);
}
clear_muxes();
delayMicroseconds(10); // smoother, minimal ghost delay
}
// ------------------------------------------------------------
// POWER UP: leds 1 → 31, continuous loop
// ------------------------------------------------------------
void power_up() {
static int last_led = -1;
for (int i = 0; i < num_muxes; i++) {
int ch_limit = (i < 4) ? 7 : 3; // last mux only has 3 channels
for (int ch = 1; ch <= ch_limit; ch++) {
int led_num = (i * 7) + ch;
// skip if repeating same LED (prevents double blink at 30)
if (led_num == last_led) continue;
light_up(mux[i], ch, 255, 1);
last_led = led_num;
}
}
digitalWrite(final_led, HIGH);
}
// ------------------------------------------------------------
// DEPOWER: leds 8 → 24, loops 3x, same visual speed
// ------------------------------------------------------------
void depower() {
const int start_led = 8;
const int end_led = 24;
const int leds_per_mux = 7;
const int start_channel = 1;
const int num_leds = (end_led - start_led) + 1;
const int per_led_delay = 30; // smoother capacitor fade
digitalWrite(final_led, HIGH); // capacitor still charged
for (int cycle = 0; cycle < 3; cycle++) {
for (int step = 0; step < num_leds; step++) {
int absolute_led = start_led + step;
int mux_index = (absolute_led - 1) / leds_per_mux;
int channel_index = ((absolute_led - 1) % leds_per_mux) + start_channel;
light_up(mux[mux_index], channel_index, 255, 1);
delay(per_led_delay);
}
}
digitalWrite(final_led, LOW); // fully discharged
}
last working code:
// multiple muxes; arjun, october 28th — noise-safe fsr + stable transitions
// ------------------------------------------------------------
// helper to get array size
// ------------------------------------------------------------
template<typename T, size_t N>
int get_array_length(T (&)[N]) {
return N;
}
// ------------------------------------------------------------
// mux setup
// ------------------------------------------------------------
int mux[5][3] = {
{ 12, 11, 10 },
{ 9, 8, 7 },
{ 6, 5, 4 },
{ 3, 2, 13 },
{ 14, 15, 16 }
};
int* get_mux_values(int channel) {
static int vals[3];
vals[0] = (channel & 0b001) ? HIGH : LOW;
vals[1] = (channel & 0b010) ? HIGH : LOW;
vals[2] = (channel & 0b100) ? HIGH : LOW;
return vals;
}
int num_muxes = get_array_length(mux);
const int channels = 8;
// ------------------------------------------------------------
// fsr + led setup
// ------------------------------------------------------------
int fsr_pin = A0;
int fsr_value = 0;
int fsr_prev_val = 0;
const int FSR_HIGH_THRESHOLD = 50; // must go above this to "press"
const int FSR_LOW_THRESHOLD = 30; // must go below this to "release"
int final_led = 18;
// ------------------------------------------------------------
// enums for state tracking
// ------------------------------------------------------------
enum FSR_Stage { LOW_TO_HIGH,
HIGH_TO_HIGH,
LOW_TO_LOW,
HIGH_TO_LOW };
FSR_Stage current_fsr_stage = LOW_TO_LOW;
enum Circuit_Stage { DISPLAYER,
POWERING,
POWERED,
DEPOWER,
END };
Circuit_Stage current_circuit_stage = DISPLAYER;
// ------------------------------------------------------------
// setup
// ------------------------------------------------------------
void setup() {
Serial.begin(9600);
for (int i = 0; i < get_array_length(mux); i++) {
for (int j = 0; j < get_array_length(mux[0]); j++) {
pinMode(mux[i][j], OUTPUT);
}
}
pinMode(fsr_pin, INPUT);
pinMode(final_led, OUTPUT);
digitalWrite(final_led, LOW);
}
// ------------------------------------------------------------
// loop
// ------------------------------------------------------------
void loop() {
fsr_value = analogRead(fsr_pin);
check_fsr_stage(fsr_value, fsr_prev_val);
Serial.println(fsr_value);
switch (current_circuit_stage) {
case DISPLAYER:
if (current_fsr_stage == LOW_TO_HIGH) {
current_circuit_stage = POWERING;
}
break;
case POWERING:
power_up();
if (current_fsr_stage == HIGH_TO_LOW) {
current_circuit_stage = DEPOWER;
}
break;
case DEPOWER:
depower();
current_circuit_stage = END;
break;
case END:
digitalWrite(final_led, LOW);
if (current_fsr_stage == LOW_TO_HIGH) {
current_circuit_stage = POWERING;
}
break;
}
fsr_prev_val = fsr_value;
}
// ------------------------------------------------------------
// helper: detect fsr transitions (with hysteresis)
// ------------------------------------------------------------
void check_fsr_stage(int current_fsr, int last_fsr) {
static bool pressed = false;
if (!pressed && current_fsr > FSR_HIGH_THRESHOLD) {
pressed = true;
current_fsr_stage = LOW_TO_HIGH;
} else if (pressed && current_fsr > FSR_LOW_THRESHOLD) {
current_fsr_stage = HIGH_TO_HIGH;
} else if (!pressed && current_fsr < FSR_LOW_THRESHOLD) {
current_fsr_stage = LOW_TO_LOW;
} else if (pressed && current_fsr < FSR_LOW_THRESHOLD) {
pressed = false;
current_fsr_stage = HIGH_TO_LOW;
}
}
// ------------------------------------------------------------
// clear all mux pins
// ------------------------------------------------------------
void clear_muxes() {
for (int i = 0; i < num_muxes; i++) {
for (int j = 0; j < 3; j++) {
digitalWrite(mux[i][j], LOW);
}
}
}
// ------------------------------------------------------------
// light up one LED (fade pulse style)
// ------------------------------------------------------------
void light_up(int input_pins[3], int channel, int brightness, int interval) {
int* values = get_mux_values(channel);
const int max_brightness = 255;
for (int b = 0; b <= brightness; b++) {
int on_time = b * interval;
int off_time = (max_brightness * interval) - on_time;
for (int i = 0; i < 3; i++) digitalWrite(input_pins[i], values[i]);
delayMicroseconds(on_time);
for (int i = 0; i < 3; i++) digitalWrite(input_pins[i], LOW);
delayMicroseconds(off_time);
}
clear_muxes();
delayMicroseconds(10);
}
// ------------------------------------------------------------
// POWER UP: leds 1 → 31, continuous loop
// ------------------------------------------------------------
void power_up() {
static int last_led = -1;
for (int i = 0; i < num_muxes; i++) {
int ch_limit = (i < 4) ? 7 : 3;
for (int ch = 1; ch <= ch_limit; ch++) {
int led_num = (i * 7) + ch;
if (led_num == last_led) continue;
light_up(mux[i], ch, 255, 1);
last_led = led_num;
}
}
digitalWrite(final_led, HIGH);
}
// ------------------------------------------------------------
// DEPOWER: leds 8 → 24, loops 3x
// ------------------------------------------------------------
void depower() {
const int start_led = 8;
const int end_led = 24;
const int leds_per_mux = 7;
const int start_channel = 1;
const int num_leds = (end_led - start_led) + 1;
const int per_led_delay = 30;
digitalWrite(final_led, HIGH);
for (int cycle = 0; cycle < 3; cycle++) {
for (int step = 0; step < num_leds; step++) {
int absolute_led = start_led + step;
int mux_index = (absolute_led - 1) / leds_per_mux;
int channel_index = ((absolute_led - 1) % leds_per_mux) + start_channel;
light_up(mux[mux_index], channel_index, 255, 1);
delay(per_led_delay);
}
}
digitalWrite(final_led, LOW);
}
to do:
learnt this from galt’s blog; started using it here.
- figure out programming
- material recce & buy
- look at led options
- order leds
- fine tune interaction
- look at material, and go to buy
- fabricate
- solder
acknowledgements:
alanna for giving me a coin-cell to test leds.
Footnotes
-
veritasium spoke to multiple physicists around the world, and sparked an online debate. in one of the videos, a professor says: “people think you’re pumping electrons, which is so wrong”. ↩
-
global-electricity consumption in 2022, from a wikipedia page: https://en.wikipedia.org/wiki/Electric_energy_consumption ↩

