Logo
  • start
LinkedIn
👾

Physical Computing Final

Physical Computing Final

Resources

p5 Web Serial Documentation

GitHub GuideGitHub Guide

Lab: Serial Input to p5.js Using the p5.webserial Library – ITP Physical ComputingLab: Serial Input to p5.js Using the p5.webserial Library – ITP Physical Computing

Adafruit Learning System Adafruit I2S MEMS Microphone BreakoutAdafruit Learning System Adafruit I2S MEMS Microphone Breakout

Paper Prototypes Planning: Unsupported EmbedUnsupported Embed

Phase 1: Testing microphone sensor

We considered airflow sensors,

Adafruit Industries Adafruit Sensirion SHTC3 Temperature & Humidity SensorAdafruit Industries Adafruit Sensirion SHTC3 Temperature & Humidity Sensor
and ended up running a first test and wiring for Adafruit I2S MEMS Microphone Breakout - SPH0645 with Arduino Nano 33 IoT:

  • LRCLK/WS connects to A2 pin on Nano
  • BCLK connects to A3 pin on Nano
  • Data/SD connects to pin 4 on Nano

Arduino Example Code 1

Arduino Code 2 Working

p5 Web Serial Trial Code

experiment_arduino_microphone by ineslucas -p5.js Web Editorexperiment_arduino_microphone by ineslucas -p5.js Web Editor

Delicate gold copy copy copy by ranjanir -p5.js Web EditorDelicate gold copy copy copy by ranjanir -p5.js Web Editor
/ / Working Code, but not the microphone ended up not being the interaction we were looking for, as you had to breath into it at an angle.

Phase 2: Setting on a concept & planning out layers

image

Phase 3: Building a Velostat circular mat

Instructables O-matInstructables O-mat
→ Our classmate Nasif was incredibly helpful in the testing phase, and we ended up verifying it works, the structure and subsequently ordering the needed quantities. Velostat purchased from
Less EMF Paints ArchivesLess EMF Paints Archives
.

The original Processing Code for a square grid is as follows below. We figured out we don’t necessarily need to use Processing as that would mean learning Java.

‣
Java Code for a squared coppered tape grid

Through the p5 prototyping below, we realised we need around 90 sections (30 below divided by 3, creating 90).

Problem: getting enough pins to connect around 90 sections

  • Can we use the I2C bus and an I2C multiplexer for analog signal from copper + velostat? Would the output of I2C communication be helpful in retrieving the range output from the pressure applied on the Velostat?
  • It seems like the Arduino Nano only takes 3 multiplexers (3 * 5 wires needed and a max of 19 Arduino pins). For the Sparkfun multiplexer
    sparkfun SparkFun Analog/Digital MUX Breakout - CD74HC4067sparkfun SparkFun Analog/Digital MUX Breakout - CD74HC4067
    , that would mean 3*16 = 48 regions of sensing, but it’s looking like we need something closer to 90. It’s possible I’m being shortseighted. Would using an array mean we need more connections or less? More actually.

Solution is basically between using an Arduino Mega with 54 pins (16 with PWM) and the Spark Fun or using more I2C multiplexers? [ Question for Prof ]

image

How to use TX / RX to connect 2 Arduinos:

Instructables I2C Between ArduinosInstructables I2C Between Arduinos

Class feedback session: Solution

  • We’re building a capacitive sensor. // custom pressure sensor.
    • When Serial is open, it can’t be open to p5 and TouchDesigner at the same time. There however can be two way serial. There’s also other ways to communicate besides serial - like Midi or OSC.
  • Main issue is that 90 is a lot of sections and a lot of code.
    • Actually we can use 2 sections ⭕️, making it 32 total or fewer sections in the palm and more in the fingertips = 15 + 30.
  • Need to verify this information but the Arduino Mega has no wifi.
  • I can chain together the Analog/Digital MUX, which means that the amount of Nano pins wouldn’t be a constraint.
  • Sparkfun Analog to digital MUX Multiplexer has a different function than the I2C multiplexer. An I2C multiplexer is for connecting I2C devices that have the same address, so that way you don’t need to change the addresses, you can target the multiplexer instead.

Next steps

Playtest where we have a few sections of the circular sensor working. Visuals can be crappy.

Fabrication

Tools used

  • Table Saw to cut up MDF
  • Laser Cutter
  • Heater for cables

Resources for creating circuit

Multiplexer

www.sparkfun.com
Datasheet (not schematic)

Operating Conditions: Supply Voltage Range, Vcc → HC Types 2V to 6V

→ When do we know if we’ve reached the maximum?

image

🍎 This may be a problem… “Only one of these is selected at any time, based on the control pins.”

How much resistance do resistors need?

To determine the resistance value for your resistor, we need to analyze the circuit and the voltage behavior of the CD74HC4067 in conjunction with the Arduino Nano IoT 33.

Voltage Output on the Channel Pins

The output voltage from any channel pin (e.g., C0) of the CD74HC4067 will depend on the input voltage supplied to the COM (SIG) pin and the control logic state. The multiplexer simply connects the selected channel (C0–C15) to the COM pin with minimal resistance.

  • Supply Voltage (VCC):
  • The CD74HC4067 works with a power supply voltage of 2–6V. If you're powering the board from the Arduino's 3.3V pin, the output voltage on any channel pin will be approximately 3.3V when HIGH.

  • Output Impedance:
  • The ON resistance of the multiplexer (when a channel is active) is around 70–100Ω (depending on VCC). This slight resistance doesn't significantly affect most circuits.

Resistor Calculation for Your Homemade Sensor

For your sensor connected to C0, the resistor to ground is likely acting as a pull-down resistor or forming a voltage divider to measure an analog signal. The resistance value depends on:

  1. Desired Voltage Range Across the Sensor:
  2. The voltage across the sensor should fall within the Arduino Nano IoT 33's ADC input range (0–3.3V). The ADC has a 12-bit resolution, so you'll get finer measurements if the voltage variation across the sensor spans as much of this range as possible.

  3. Sensor Characteristics:
  4. What is the expected resistance range of your homemade sensor? This is critical for determining the pull-down resistor.

Case 1: Sensor as a Variable Resistor

image

​

image

Case 2: Fixed Resistor for Digital Input

If your sensor output is binary (e.g., HIGH/LOW detection), use a pull-down resistor:

  • Typical values: 10kΩ works well in most cases.
💡

C0 → Sensor → GND (through pull-down resistor).

We have 27 pins left! 🍓

Writing code to read sensors

‣
Small Mat Experiment code by Nasif Rincon
‣
Code by Instructables for when all pins are connected to sensors plus using Arduino Mini Pro 5V

Moving to VS Code & a React app with p5

NANO 33 IoT — PlatformIO v6.1 documentationNANO 33 IoT — PlatformIO v6.1 documentation

Start project with Arduino MKR WiFi 1010 as a replacement for Nano 33 IoT

‣
Prompt for correct pins

Thanks, that's great. Both multiplexers have the same function, where their I/O Channel pins are all connected to copper. A sandwich of copper, velostat and copper is what gives a reading.

Ideally, i'd have a 3 by 18 matrix with 54 sensors.

So should the 2 multiplexers be treated as input vs output, since they both serve the same function?

Also, my matrix currently has 3 rows and 18 columns. The rows should read a range analog signal.

The 3 rows are connected to pins C0, C1 and C2 to the multiplexer whose SIG pin is connected to Arduino's A0 pin. For that multiplexer, S3 pin is connected to A1 arduino pin, S2 is connected to A2, S1 to A3 and S0 to A4.

The first 9 columns are connected to pins C8, C7, C6, C5, C4, C3, C2, C1 and C0 on the multiplexer whose SIG pin is connected to Arduino's D08 pin. For that multiplexer, S3 pin is connected to D09 Arduino pin, S2 to D10, S1 to D11 and S0 to D12.

The second set of 9 colums are connected to pins C11, C10, C9, C8, C7, C6, C5, C4 and C3 on the multiplexer whose SIG pin is connected to Arduino's A0 pin. Again for this multiplexer, S3 pin is connected to A1 arduino pin, S2 is connected to A2, S1 to A3 and S0 to A4.

I want you to help me modify my code so that the connections are all correct and even help me read the value of the rows and colums in the order I outlined.

Prompt #2:

I followed your instructions, the monitor is printing As!

The next step is making these readings useful.

I want to read values for mux1, channels C0, C1 and C2. Meaning - the rows, just as an example + print on the monitor the readings from those pins.

How can I set that up?

Let me know also about next steps, if there's any calibration that still needs to be done or something else.

The goal is to eventually map those readings to output a value from 0 to 20 for each of the 54 sensors. The idea is to receive the data in a different React application and when the copper columns + velostat + copper rows get pressure, the number seen on the screen for each of the 54 quadrants would increase accordingly.

‣
Code for getting baseline readings
💡
  • First 9 columns (C8 down to C0) from the second multiplexer (D8)
  • Next 9 columns (C11 down to C3) from the first multiplexer (A0)
image
image
image
image

What we did:

  • finished hooking up sensors
  • Figured out the circuit
  • Chained multiplexers
  • Added p5 to a React application, started setting up the structure to receive and handle API data for top stories
  • Added a way to real serial monitor on VS Code
  • Wrote the code for chaining and reading 54 sensors
  • Wrote the code to do first baseline readingsz

Feedback from Playtesting #1:

News showing is it bad? Or chaotic? Could show comments instead of news.

Youtube Trending Videos

Visuals:

  • can use text for news - text rotating circles like in news channels

Will it be hard to get

Can use felt and

What if we visualize each news story as its own organism?

Play act or

Next week: bring an actual visuals!

Next steps - AI reference advice

‣
Code

Next steps would be:

Calibration Phase:

  • Take readings when no pressure is applied (baseline)
  • Take readings with light pressure
  • Take readings with maximum expected pressure
  • This helps establish your min/max range for mapping

Data Processing:

Serial Communication Format:

For React integration, you'll want a consistent data format. Here's one approach:

React Integration:

Your React app would need to:

  • Connect to the serial port (using a library like serialport)
  • Parse the JSON data
  • Update your UI based on the values

Would you like me to provide a more detailed implementation of any of these steps? Also, do you want to start by testing the row readings first to see what kind of values you're getting?

‣
Testing prompt 2x3 rows

Code for 2x3 matrix

Monitor readings from code above after resetting arduino

‣
Question about pinMode

For the Arduino Nano IoT 33, what is the function that allows us to analog read the A0 pin?

Does function pinMode also work to set analog pins as INPUT or OUTPUT or is it only for digital pins?

image

Project building: prep for playtest 2

@December 2, 2024

Tasks aren’t building with the same code.

image

If I reset → unplug → build - monitor doesn’t complete successfully ❌

If I reset without unplugging, monitor reads ✅

But current readings aren’t running.

React can’t read if platformIO is reading.

Print out pics of what’s happening → For interaction.

multiplexee does the same as the for loop

variable resistor = we have an homemade w velostat

there’s a multiplexer library??!

Assumptions: do people understand the touch grass thing? Does the concept make sense.

Make sure to handle cleanup properly in your React component (closing the serial port when the component unmounts)

Debug

  • I can list all the ports + select port from the terminal.
  • ls /dev/cu.*
    image
  • Only one thing can be connected at a time. It’s not broadcasted.
  • Don’t get rid of if serial.
  • I need I close serial monitor,
    • Have p5 send a 0, so that it knows when it’s been pinged. Same as I was sending over
    • Mouse press could be the condition and then it reads automatically. Make it user initiated. Don’t do a while loop.

Lab: Serial Input to p5.js Using the p5.webserial Library – ITP Physical ComputingLab: Serial Input to p5.js Using the p5.webserial Library – ITP Physical Computing

WebSerial communicates over the browser.

Lab: Serial Input to P5.js Using the p5.serialport library – ITP Physical ComputingLab: Serial Input to P5.js Using the p5.serialport library – ITP Physical Computing

It communicates with the p5.serialcontrol app, a WebSocket server that gives you access to serial devices attached to your computer.

p5.serialport and p5.webserial Compared – ITP Physical Computingp5.serialport and p5.webserial Compared – ITP Physical Computing

Physical Computing L4: p5.js Serial I/OPhysical Computing L4: p5.js Serial I/O
by the Makeability Lab

p5xjs Creating Custom Geometry in WebGLp5xjs Creating Custom Geometry in WebGL

npm npm: @p5-wrapper/reactnpm npm: @p5-wrapper/react

Basic usage of @p5-wrapper/react

As you work on this any microcontroller-to-computer application, you will be switching back and forth between the app that programs the microcontroller (in this case, the Arduino IDE) and the app that the microcontroller is communicating with (in this case, p5.js in the browser). You have to keep in mind that only one of these at a time can access a serial port.

That means that when you want to reprogram your Arduino from the Arduino IDE, you should to stop your sketch in the browser window to do so. Then, restart the browser sketch when you’re done reprogramming the Arduino. You don’t need to quit the Arduino IDE each time, because it knows to release the serial port when it’s not programming. However, you do need to close the Serial Monitor in the Arduino IDE when you are using WebSerial in the browser.

We changed the Arduino Code to continuously read, but C1 is still off the charts. Is it because electricity takes the path of least resistance?

image

Code for Ripples on p5 editor - check if flower or ripple output without p5.WebSerial library

image

Possibility to improve cleanup on React code - not implemented.

For these 3 error messages:

  • The RAF (RequestAnimationFrame) violation - this is just a performance warning and not related to our serial connection
  • The message port error - this is a Chrome-specific message that's actually not an error, just a notification
  • The Bluetooth service message - this is just informing us that AirPods were filtered out of the serial port selection

Playtest #2 Feedback:

  • Don’t use real news
    • Overwhelming cloud of negative words
    • AI generated Gibberish

If you put the installation on the floor people will step on it.

❤️ Grounding ❤️

When you come in, i should

Claspy mechanisms / things

Nasif likes when name contextualizes

Rubbery gas kit - for rubber Talk to phil 2 clasps against each other L brackets

Digital noise that dissipates with collective play

Demo present or video/pics to submit Have people interact Class can go to staging space

ICM Visual Questions:

  • Effect of text melting / dissipating into a swirl → into a pond
  • setup function in p5 is being fired repeatedly
    • Looked into GitHub issues → could be because of States
    • I’m also putting the variables in a cleanup function.
    • using the updateWithProps p5 react wrapper function.
image

Embed GitHub

FastAPI in Containers - Docker - FastAPIFastAPI in Containers - Docker - FastAPI

Solving issue of React re-rendering setup p5 function → switching from state to refs

Key changes

  • Replaced all useState with useRef
  • Added refs for visualization states (ripples, lotuses, caustics)
  • Removed state updates that were causing re-renders
  • Used p5Instance.current?.redraw() to trigger redraws only when needed
  • Removed dependency array from cleanup useEffect since we're using refs
  • Modified all state access to use .current property of refs

The visualization will now update only when new serial data arrives or when explicitly redrawn.

Oceanic whippet by ranjanir -p5.js Web EditorOceanic whippet by ranjanir -p5.js Web Editor

WhatRuns - Chrome Web StoreWhatRuns - Chrome Web Store

p5xjs Setting Up Your Environmentp5xjs Setting Up Your Environment

npm npm: p5npm npm: p5

Connecting all 36 columns x 3 rows = 108 home made FSRs

‣
Explanation of circle shape and connections between multiplexers

Now we'll be adding more sensors. Especifically we now have a table of 3 rows and 36 columns for the pressure sensor matrix, instead of 3 times 9. I'm now using 3 multiplexers.

it's important that the 108 values are output in the right order.

The rows are still reading:

A0 - top row

A1 - middle row

A2 - bottom row

The 36 columns are connected through multiplexers.

This is how the multiplexers are connected to the Arduino

First multiplexer - let's call it MTL for Multiplexer Top Left:

SIG pin of the multiplexer- D8 pin of the arduino

S3 - D9

S2 - D10

S1 - D11

S0 - D12

Pins C15 to C7 are connected to the first 9 columns, where first column is connected to C15, second column to pin C14 and so on.

Pins C3 to C0 are connected to columns 10, 11, 12 and 13.

Second multiplexer - let's call it MBL for Multiplexer Bottom Left:

SIG - A3 pin of the arduino

S3 - A4

S2 - A5

S1 - A6

S0 - A7

On this second multiplexer MBL, pins C15 to C11 are connected to column 14 to column 18. Pins C10 to C2 are connected to column 19 to 27

Third multiplexer - let's call it MTR for Multiplexer Top Right:

SIG - D7 pin of the arduino

S3 - D6

S2 - D5

S1 - D4

S0 - D3

On multiplexer MTR, column 28 to 36 are connected to pins C15 to C7.

Can you write code that will output all combinations of 3 rows and 36 columns in the right order from column 1 to 36, measuring row 1 and col 1 first, then row 2, col 1, then row 3, col 1 and at last row 1, col 36, then row 2, col 36 and last value would be row 3, col 36?

Leave comments so I understand what your decisions have been

Fabrication

For the Optoma projector to work, we’d need to raise the structure by 15 inches~

Brooklyn - @glassEagle on Scaniverse
‣

Asked AI what the code is doing:

I'll break down your current code's functionality:

  • Hardware Setup:
  • Uses 3 analog input pins (A0, A1, A2) for rows
  • Uses one multiplexer connected to digital pins 8-12
  • Configuration is for a 3×9 pressure sensor matrix
  • Main Components:
  • int currentValues[3][9];    // Stores current sensor readings

    int baselineValues[3][9];   // Stores calibration values

  • Program Flow:
  • Setup Phase:
  • Initializes serial communication at 9600 baud
  • Configures row pins as inputs
  • Configures multiplexer control pins as outputs
  • Runs initial calibration
  • Calibration Process (calibrate()):
  • Takes 20 samples for each sensor
  • Averages these readings to establish a baseline
  • This helps eliminate ambient noise/pressure
  • Main Loop:
  • Continuously reads the entire sensor matrix
  • Outputs data in CSV format
  • Runs every 20ms (50 times per second)
  • Reading Process (readMat()):
  • For each column:
  • Sets appropriate multiplexer channel
  • Activates the signal pin
  • Reads all rows for that column

4. Subtracts baseline values

  • Clamps negative values to 0
  • Data Output:
  • Sends data in CSV format: value1,value2,value3,...
  • Each row is separated by commas
  • Each complete reading ends with a newline
  • Key Features:
  • Baseline subtraction for noise reduction
  • Non-negative value clamping
  • Fast sampling rate (50Hz)
  • Machine-readable CSV output format

This code appears to be designed for real-time pressure sensing with automatic baseline calibration and continuous monitoring.

Measurements for final projection surface:

  • 27’’ exagerated - any LED panel should have 27’’
    • Diagonal: 38.18’’ so 38’’
  • 23’’ actual

What’s the maximum dimension that this monitor can have?

I made Raspberry Pi driven large LED matrix with various features : r/raspberry_pi

Touch Grass is an interactive digital pond. Its shape ensures everyone has equal stakes and engaging in accessible collective play, which creates a state of mind for open conversation. It conveys the power of collaboration in society, as you need everyone to touch it as the same time to see the full ripple effects. With more iterations, it could be an interface for brainstorm and shared physical computing. Currently it's very large, unstable and difficult to bring to where it can have an impact. The grant is to manufacture of a custom round 32'' PCB LED display that can replace the bin, projector, metal stand and counterweight setup. Afterwards, I'll explore more direct ways to spark conversation at a time where we need communities to hold and explore their differences.

Folk Computer due to how it can bring elders and children alike together to brainstorm together and solve societal issues without the downside of addiction that's often caused by more portable forms of tech, the relationship between art materials, supply chain and tariffs, as well as Cyber Feminism.

More hackathons solving specific issues shared by studios, policy makers or communities with very real constraints. Vibe Shift was an event where I saw people from all parts of creative tech, which was very positive. But as the world shifts with AI, learning outside of a typical classroom through events, hackathons and crash courses followed by working on your own projects solving very specific issues that exist outside of a bubble is the way forward - as opposed to studying, memorizing and solving self-selected arbitrary problems, where you choose your constraints.

/*
 This example reads audio data from an Invensense's ICS43432 I2S microphone
 breakout board, and prints out the samples to the Serial console. The
 Serial Plotter built into the Arduino IDE can be used to plot the audio
 data (Tools -> Serial Plotter)

 Circuit:
 * Arduino/Genuino Zero, MKR family and Nano 33 IoT
 * ICS43432:
   * GND connected GND DONE
   * 3.3V connected to 3.3V (Zero, Nano) or VCC (MKR) DONE
   * (LRCLK or) WS connected to pin 0 (Zero) or 3 (MKR) or A2 (Nano) 
   * CLK connected to pin 1 (Zero) or 2 (MKR) or A3 (Nano)
   * SD connected to pin 9 (Zero) or A6 (MKR) or 4 (Nano)

 created 17 November 2016
 by Sandeep Mistry
 */

#include <I2S.h>

void setup() {
  // Open serial communications and wait for port to open:
  // A baud rate of 115200 is used instead of 9600 for a faster data rate
  // on non-native USB ports
  Serial.begin(115200);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // start I2S at 8 kHz with 32-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }
}

void loop() {
  // read a sample
  int sample = I2S.read();

  if (sample) {
    // if it's non-zero print value to serial
    Serial.println(sample);
  }
}
#include <I2S.h>

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    ; // Wait for serial port to connect. Needed for native USB only
  }

  // Start I2S at 8 kHz with 32-bits per sample
  if (!I2S.begin(I2S_PHILIPS_MODE, 8000, 32)) {
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }
}

void loop() {
  // Read a sample from the I2S microphone
  int sample = I2S.read();

  // Only send significant audio levels
  if (sample > 100) { // Threshold to filter out background noise
    Serial.println(abs(sample) / 100); // Scale down data for p5.js
  }
}
// This example code is in the public domain.

import processing.serial.*;
import processing.opengl.*;


int bgcolor;                 // Background color
int fgcolor;                 // Fill color
Serial myPort;                       // The serial port
int[] serialInArray = new int[225];    // Where we'll put what we receive
int[] pastInArray = new int [225];
float[][] colorTarget   = new float[3][255];
float[][] currentColor   = new float[3][255];
PVector[][] vertices = new PVector[15][15];
float[] verticesTZ = new float[15];
float w = 30;
float ease = 0.75; 

int serialCount = 0;                 // A count of how many bytes we receive
int xpos, ypos;                  // Starting position of the ball
boolean firstContact = false;        // Whether we've heard from the microcontroller
int tiempoant;
int render=0;
int dif=0;

void setup() {
  size(960, 600, OPENGL);  // Stage size
  noStroke();      // No border on the next thing draw
  
  
  // Print a list of the serial ports, for debugging purposes:
  println(Serial.list());

  // I know that the first port in the serial list on my mac
  // is always my  FTDI adaptor, so I open Serial.list()[0].
  // On Windows machines, this generally opens COM1.
  // Open whatever port is the one you're using.
  
  myPort = new Serial(this, Serial.list()[5], 115200);
  
  for (int j = 0; j < 15; j++) {
        for (int i = 0; i < 15; i++) {
            vertices[i][j] = new PVector( i*w, j*w, 0);
        }
    }
    
  
}

void draw() {

  if (render==1) {
    
    translate(width/4, 100);
    rotateX(0.5);
    //rotateX(PI/10);
    background(0);
    for (int j=0; j<14; j++) {
      beginShape(QUAD_STRIP);
      for (int i=0; i<15; i++) {
          stroke(255);
     
          fill(serialInArray[j*15+i], 0, 0);
          float x = i*width/15;
          float y = j*height/15;
          verticesTZ[i] = serialInArray[j*15+i];
          
          vertices[i][j].z += (verticesTZ[i]-vertices[i][j].z)*ease;
          vertex( vertices[i][j].x, vertices[i][j].y, vertices[i][j].z);
          vertex( vertices[i][j+1].x, vertices[i][j+1].y, vertices[i][j+1].z);
        }
         endShape(CLOSE);
        //        println();
      }
      render=0;
  }
}

void serialEvent(Serial myPort) {
  // read a byte from the serial port:
  int inByte = myPort.read();
  // if this is the first byte received, and it's an A,
  // clear the serial buffer and note that you've
  // h
  //  ad first contact from the microcontroller. 
  // Otherwise, add the incoming byte to the array:
  if (firstContact == false) {
    if (inByte == 'A') { 
      myPort.clear();          // clear the serial port buffer
      firstContact = true;     // you've had first contact from the microcontroller
      myPort.write('A');       // ask for more
    }
  } else {
    // Add the latest byte from the serial port to array:

      serialInArray[serialCount] = inByte;

    serialCount++;

    // If we have 3 bytes:
    if (serialCount > 224 ) {
      println(millis()-tiempoant);
      tiempoant = millis();
      
      render = 1;
    
      // Send a capital A to request new sensor readings:
      myPort.write('A');
      // Reset serialCount:
      serialCount = 0;
    }
  }
}
//Pins for the rows and columns in order
int row[] = {12, 11, 10, 9, 8};
int column[] = {A1, A2, A3, A4, A5};

//Array that holds calibration values
int calibration[5][5];

//Variables used to process readings
int minsensor = 254;
int reading = 0;
const int cap = 1000; //cap for the reading values

void setup() {
  //Init serial
  Serial.begin(112500);

  //Set the pin modes
  for (int i = 0; i < 5; i++) {
    pinMode(row[i], OUTPUT);
    pinMode(column[i], INPUT);
  }

  calibrate();
}
int inByte = 0;
void loop() {
  if(Serial.available() > 0){
    inByte = Serial.read();
    //Serial.print(inByte);
    if(inByte == 48){
      readMat();
//      for(int i=0; i < 5; i++){
//        delay(100);
//        Serial.write(i);
//      }
    }
  }
  
  //delay(100);
  //clearConsole();
}

void calibrate() {
  //Initialize the calibration at 0
  for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
      calibration[i][j] = 0;
    }
  }

  //Read each sensor 50 times and get the sum of its readings
  for (int k = 0; k < 50; k++) {
    for (int i = 0; i < 5; i++) {
      digitalWrite(row[i], HIGH);
      for (int j = 0; j < 5; j++) {
        calibration[i][j] = calibration[i][j] + analogRead(column[j]);
      }
      digitalWrite(row[i], LOW);
    }
  }

  //Divide the sums by 50 to get the average readings of each sensor
  for (int i = 0; i < 5; i ++) {
    for (int j = 0; j < 5; j ++) {
      calibration[i][j] = calibration[i][j] / 50;
      if (calibration[i][j] < minsensor) {
        minsensor = calibration[i][j];
      }
    }
  }

//  Serial.println();
//  Serial.print("Minimum Value: ");
//  Serial.println(minsensor);
//  Serial.println();
}

void readMat() {
  for (int i = 0; i < 5; i++) {
    digitalWrite(row[i], HIGH);
    for (int j = 0; j < 5; j++) {
      //Read the pixels
      reading = analogRead(column[j]);

      //Limit the reading between its
      //standby calibration value and
      //our predefined max value
      reading = constrain(reading, calibration[i][j], cap);

      //Map reading to a byte
      reading = map(reading, minsensor, cap, 1, 254);

      //Addition upper and lower cap
      if (reading < 50) {
        reading = 0;
      } else if (reading > 254) {
        reading = 254;
      }

      if(reading > 0) {
        reading = map(reading, 50, 254, 0, 254);
      }

      //Format to three digits
//      char buffer[4]; // Create a buffer for the formatted string
//      sprintf(buffer, "%03d", reading); // Format the number as 3 digits with leading zeros
//      //Print the reading
//      Serial.print(buffer);
//      Serial.print(' ');
      //delay(10);
      Serial.write(reading);
    }
    digitalWrite(row[i], LOW);
    //Jump to the next row in the Serial
    //Serial.println();
  }
}

//void clearConsole() {
//  for (int i = 0; i < 50; i++) { // Adjust the number of lines as needed
//    Serial.println();
//  }
//}
//Mux control pins for analog signal (SIG_pin) default for arduino mini pro
const byte s0 = 13;
const byte s1 = 12;
const byte s2 = 11;
const byte s3 = 10;

//Mux control pins for Output signal (OUT_pin) default for arduino mini pro
const byte w0 = 9; 
const byte w1 = 8;
const byte w2 = 7;
const byte w3 = 6;

//Mux in "SIG" pin default for arduino mini pro 
const byte SIG_pin = 0; 

//Mux out "SIG" pin default for arduino mini pro
const byte OUT_pin = 5;

//Row and Column pins default for arduino mini pro
const byte STATUS_pin = 3;
const byte COL_pin = 2;

const boolean muxChannel[16][4]={
    {0,0,0,0}, //channel 0
    {1,0,0,0}, //channel 1
    {0,1,0,0}, //channel 2
    {1,1,0,0}, //channel 3
    {0,0,1,0}, //channel 4
    {1,0,1,0}, //channel 5
    {0,1,1,0}, //channel 6
    {1,1,1,0}, //channel 7
    {0,0,0,1}, //channel 8
    {1,0,0,1}, //channel 9
    {0,1,0,1}, //channel 10
    {1,1,0,1}, //channel 11
    {0,0,1,1}, //channel 12
    {1,0,1,1}, //channel 13
    {0,1,1,1}, //channel 14
    {1,1,1,1}  //channel 15
  };


//incoming serial byte
int inByte = 0;

int valor = 0;               //variable for sending bytes to processing
int calibra[15][15];         //Calibration array for the min values of each od the 225 sensors.
int minsensor=254;          //Variable for staring the min array
int multiplier = 254;
int pastmatrix[15][15];

void setup(){
    
  pinMode(s0, OUTPUT); 
  pinMode(s1, OUTPUT); 
  pinMode(s2, OUTPUT); 
  pinMode(s3, OUTPUT); 
  
  pinMode(w0, OUTPUT); 
  pinMode(w1, OUTPUT); 
  pinMode(w2, OUTPUT); 
  pinMode(w3, OUTPUT); 
  
  pinMode(OUT_pin, OUTPUT); 
  
  pinMode(STATUS_pin, OUTPUT);
  pinMode(COL_pin, OUTPUT);

  
  digitalWrite(s0, LOW);
  digitalWrite(s1, LOW);
  digitalWrite(s2, LOW);
  digitalWrite(s3, LOW);
  
  digitalWrite(w0, LOW);
  digitalWrite(w1, LOW);
  digitalWrite(w2, LOW);
  digitalWrite(w3, LOW);
  
  digitalWrite(OUT_pin, HIGH);
  digitalWrite(STATUS_pin, HIGH);
  digitalWrite(COL_pin, HIGH);
  
 
  
  Serial.begin(115200);
  
  Serial.println("\n\Calibrating...\n");
  
  // Full of 0's of initial matrix
  for(byte j = 0; j < 15; j ++){ 
    writeMux(j);
    for(byte i = 0; i < 15; i ++)
      calibra[j][i] = 0;
  }
  
  // Calibration
  for(byte k = 0; k < 50; k++){  
    for(byte j = 0; j < 15; j ++){ 
      writeMux(j);
      for(byte i = 0; i < 15; i ++)
        calibra[j][i] = calibra[j][i] + readMux(i);
    }
  }
  
  //Print averages
  for(byte j = 0; j < 15; j ++){ 
    writeMux(j);
    for(byte i = 0; i < 15; i ++){
      calibra[j][i] = calibra[j][i]/50;
      if(calibra[j][i] < minsensor)
        minsensor = calibra[j][i];
      Serial.print(calibra[j][i]);
      Serial.print("\t");
    }
  Serial.println(); 
  }
  
  Serial.println();
  Serial.print("Minimum Value: ");
  Serial.println(minsensor);
  Serial.println();
  
  establishContact();
 
  digitalWrite(COL_pin, LOW);
}


void loop(){
  //Loop through and read all 16 values
  //Reports back Value at channel 6 is: 346
  if (Serial.available() > 0){
    inByte = Serial.read();
    
    if(inByte == 'A'){
    
      for(int j = 14; j >= 0; j--){ 
        writeMux(j);
        
        for(int i = 0; i < 15; i++){
            
          valor = readMux(i);
          
          //Saturation sensors
          int limsup = 450;
          if(valor > limsup)
            valor = limsup;
            
          if(valor < calibra[j][i])
            valor = calibra[j][i];  
          
          valor = map(valor,minsensor, limsup,1,254); 
          
          if(valor < 150)
            valor = 0;
          if(valor > 254)
            valor = 254;
          
          Serial.write(valor);
          digitalWrite(COL_pin,!digitalRead(COL_pin));
        } 
      }
    }
        
  }
}


int readMux(byte channel){
  byte controlPin[] = {s0, s1, s2, s3};

  //loop through the 4 sig
  for(int i = 0; i < 4; i ++){
    digitalWrite(controlPin[i], muxChannel[channel][i]);
  }

  //read the value at the SIG pin
  int val = analogRead(SIG_pin);

  //return the value
  return val;
}

void writeMux(byte channel){
  byte controlPin[] = {w0, w1, w2, w3};

  //loop through the 4 sig
  for(byte i = 0; i < 4; i ++){
    digitalWrite(controlPin[i], muxChannel[channel][i]);
  }
}

void establishContact() {
  while (Serial.available() <= 0) {
    Serial.print('A');   // send a capital A
    delay(300);
  }
}

#include <Arduino.h>

// First multiplexer (connected to A0) - handles rows and second set of columns
const byte mux1_s0 = A4;  // Control pins for first multiplexer
const byte mux1_s1 = A3;
const byte mux1_s2 = A2;
const byte mux1_s3 = A1;
const byte mux1_sig = A0; // Signal pin

// Second multiplexer (connected to D8) - handles first set of columns
const byte mux2_s0 = 12;  // Control pins for second multiplexer
const byte mux2_s1 = 11;
const byte mux2_s2 = 10;
const byte mux2_s3 = 9;
const byte mux2_sig = 8;  // Signal pin

// Mux channel configuration
const boolean muxChannel[16][4] = {
    {0, 0, 0, 0}, // Channel 0
    {1, 0, 0, 0}, // Channel 1
    {0, 1, 0, 0}, // Channel 2
    {1, 1, 0, 0}, // Channel 3
    {0, 0, 1, 0}, // Channel 4
    {1, 0, 1, 0}, // Channel 5
    {0, 1, 1, 0}, // Channel 6
    {1, 1, 1, 0}, // Channel 7
    {0, 0, 0, 1}, // Channel 8
    {1, 0, 0, 1}, // Channel 9
    {0, 1, 0, 1}, // Channel 10
    {1, 1, 0, 1}, // Channel 11
    {0, 0, 1, 1}, // Channel 12
    {1, 0, 1, 1}, // Channel 13
    {0, 1, 1, 1}, // Channel 14
    {1, 1, 1, 1}  // Channel 15
};

// Function declarations
int readMux1(byte channel);
int readMux2(byte channel);

void setup() {
  // Configure pins for first multiplexer
  pinMode(mux1_s0, OUTPUT);
  pinMode(mux1_s1, OUTPUT);
  pinMode(mux1_s2, OUTPUT);
  pinMode(mux1_s3, OUTPUT);
  pinMode(mux1_sig, INPUT);

  // Configure pins for second multiplexer
  pinMode(mux2_s0, OUTPUT);
  pinMode(mux2_s1, OUTPUT);
  pinMode(mux2_s2, OUTPUT);
  pinMode(mux2_s3, OUTPUT);
  pinMode(mux2_sig, INPUT);

  Serial.begin(115200);
  Serial.println("Starting row readings test...");
  Serial.println("Reading channels C0, C1, C2 from first multiplexer");
  Serial.println();
}

void loop() {
  // TESTING ROWS CODE 🚨
  for (int row = 0; row < 3; row++) {
    int rowValue = readMux1(row);  // Read channels 0, 1, and 2
    
    Serial.print("Row ");
    Serial.print(row);
    Serial.print(": ");
    Serial.print(rowValue);
    Serial.print("\t");
  }
  Serial.println(); // New line after printing all rows
  delay(500);      // Wait half a second before next reading
}

// Read from first multiplexer (connected to A0)
int readMux1(byte channel) {
  byte controlPin[] = {mux1_s0, mux1_s1, mux1_s2, mux1_s3};
  for (int i = 0; i < 4; i++) {
    digitalWrite(controlPin[i], muxChannel[channel][i]);
  }
  return analogRead(mux1_sig);
}

// Read from second multiplexer (connected to D8)
int readMux2(byte channel) {
  byte controlPin[] = {mux2_s0, mux2_s1, mux2_s2, mux2_s3};
  for (int i = 0; i < 4; i++) {
    digitalWrite(controlPin[i], muxChannel[channel][i]);
  }
  return analogRead(mux2_sig);
}
// Example mapping function
int mapPressure(int rawValue, int row, int col) {
  // Define your calibration values
  const int NO_PRESSURE = 100;    // Example value when no pressure
  const int MAX_PRESSURE = 900;   // Example value at maximum pressure
  
  // Map the raw value to 0-20 range
  int mappedValue = map(rawValue, 
                       NO_PRESSURE, 
                       MAX_PRESSURE, 
                       0, 
                       20);
                       
  // Constrain to ensure we stay in range
  mappedValue = constrain(mappedValue, 0, 20);
  
  return mappedValue;
}
void sendMatrixData() {
  Serial.print("{");
  for (int row = 0; row < 3; row++) {
    for (int col = 0; col < 18; col++) {
      int value = // ... your reading code ...
      int mappedValue = mapPressure(value, row, col);
      
      Serial.print("\"");
      Serial.print(row);
      Serial.print(",");
      Serial.print(col);
      Serial.print("\":");
      Serial.print(mappedValue);
      
      if (!(row == 2 && col == 17)) {
        Serial.print(",");
      }
    }
  }
  Serial.println("}");
}
We're using this arduino code to test 3 rows and 2 columns of our mat. We're debugging, and therefore no longer using multiplexers for the moment.
I remind you that our structure is writing to copper rows, then there's a layer of velostat and lastly, one of copper columns.

The following arduino pins need to be turned on HIGH, one at a time to HIGH digital out:
D2 -> first column
D3  -> second column

They will be checked across the analog rows to check for values. The following arduino pins are connected and we need to analog read them:
A0 -> top row
A1 -> middle row
A2 -> bottom row 

We need to basically cycle through them using 2 for loops that are nested.
Help us write the code for this, so we can:
1. Calibrate - come up with average values so we can see a difference once we touch our mat
2. See on the serial monitor the values for all 6 sensors (3 rows x 2 cols).
////////////// TEST READINGS FOR 3 ROWS AND 2 COLS ///////////////
#include <Arduino.h>

// Pin definitions
const int ROW_PINS[] = {A0, A1, A2};  // Analog input pins for rows
const int COL_PINS[] = {2, 3};        // Digital output pins for columns
const int NUM_ROWS = 3;
const int NUM_COLS = 2;

// Arrays to store sensor values
int currentValues[3][2];    // Current readings
int baselineValues[3][2];   // Calibration baseline
const int NUM_CALIBRATION_SAMPLES = 20;

// Function declarations
void calibrate();
void readMat();

void setup() {
  // Init Serial
  Serial.begin(9600); // prev using 112500 baud rate

  // We need to loop through each of the 3 rows
  // then set row 0 as Analog Read
  // then set column 0 as HIGH digital out
  // then repeat the process using the 2 nested for loops

  // Set pin modes
  for (int r = 0; r < NUM_ROWS; r++) {
    pinMode(ROW_PINS[r], INPUT); // Analog
    // Pin value still needs to be read later on.
    // This just sets pin X as Input or Output
  }

  for (int c = 0; c < NUM_COLS; c++) {
    pinMode(COL_PINS[c], OUTPUT);
    digitalWrite(COL_PINS[c], LOW);  // Start with all columns LOW
  }

  delay(1000);  // Add a delay to allow serial to initialize
  Serial.println("Starting up...");  // Add this to verify serial is working

  // Initial calibration
  calibrate();
}

void calibrate() {
  Serial.println("Starting calibration...");

  // Reset baseline values
  for (int r = 0; r < NUM_ROWS; r++) {
    for (int c = 0; c < NUM_COLS; c++) {
      baselineValues[r][c] = 0;
    }
  }

  // Take multiple samples
  for (int sample = 0; sample < NUM_CALIBRATION_SAMPLES; sample++) {
    for (int c = 0; c < NUM_COLS; c++) {
      // Activate one column at a time
      digitalWrite(COL_PINS[c], HIGH);
      delay(10);  // Small delay for stability

      // Read all rows for this column
      for (int r = 0; r < NUM_ROWS; r++) {
        baselineValues[r][c] += analogRead(ROW_PINS[r]);
        // Creating a sum
      }

      digitalWrite(COL_PINS[c], LOW);
      // setting the column pins back to LOW, as we're done with calibration
    }
  }

  // Calculate averages
  for (int r = 0; r < NUM_ROWS; r++) {
    for (int c = 0; c < NUM_COLS; c++) {
      baselineValues[r][c] /= NUM_CALIBRATION_SAMPLES;
      Serial.print("Baseline R");
      Serial.print(r);
      Serial.print("C");
      Serial.print(c);
      Serial.print(": ");
      Serial.println(baselineValues[r][c]);
    }
  }

  Serial.println("Calibration complete!");
}

int inByte = 0; // Initializing inByte and setting to 0


void readMat() {
  // Read all sensors
  for (int c = 0; c < NUM_COLS; c++) {
    // Activate one column at a time
    digitalWrite(COL_PINS[c], HIGH);
    delay(10);  // Small delay for stability

    // Read all rows for this column
    for (int r = 0; r < NUM_ROWS; r++) {
      currentValues[r][c] = analogRead(ROW_PINS[r]);
    }

    digitalWrite(COL_PINS[c], LOW);
  }

  // Print the readings
  Serial.println("\n--- Current Readings ---");
  for (int r = 0; r < NUM_ROWS; r++) {
    for (int c = 0; c < NUM_COLS; c++) {
      int difference = currentValues[r][c] - baselineValues[r][c];

      Serial.print("R");
      Serial.print(r);
      Serial.print("C");
      Serial.print(c);
      Serial.print(": ");
      Serial.print(currentValues[r][c]);
      Serial.print(" (diff: ");
      Serial.print(difference);
      Serial.print(")  ");
    }
    Serial.println();
  }
}

void loop() {
  if (Serial.available() > 0) {
    int inByte = Serial.read();
    if (inByte == '0') {  // Changed to char '0' (ASCII 48)
      readMat();
    } else if (inByte == 'c') {  // Added option to recalibrate
      calibrate();
    }
  }
}
 *  Executing task: platformio device monitor --environment nano_33_iot 

--- Terminal on /dev/cu.usbmodem1101 | 9600 8-N-1
--- Available filters and text transformations: colorize, debug, default, direct, hexlify, log2file, nocontrol, printable, send_on_enter, time
--- More details at https://bit.ly/pio-monitor-filters
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H
Starting up...
Starting calibration...
Baseline R0C0: 5
Baseline R0C1: 1
Baseline R1C0: 5
Baseline R1C1: 4
Baseline R2C0: 3
Baseline R2C1: 2
Calibration complete!
import { ReactP5Wrapper } from "@p5-wrapper/react";

const P5sketch = () => {
  function sketch(p5) {
    p5.setup = () => {
      p5.createCanvas(700, 400);
      p5.background(200);
    };

    p5.draw = () => {
      p5.fill(181, 79, 111);
      p5.noStroke();
      p5.rect(200, 200, 100, 50);  // x, y, width, height
    };
  }

  return <ReactP5Wrapper sketch={sketch} />;
};

export default P5sketch;
let sensorValues = []; // Array to store sensor readings
let ripples = [];
let lotuses = [];
let caustics = [];
let serial; // WebSerial object
let port; // Port object
let reader; // Reader for serial data
let decoder = new TextDecoder(); // Decoder for incoming serial data

function setup() {
  createCanvas(800, 600);
  noSmooth();

  // Initialize caustics grid
  for (let i = 0; i < width; i += 20) {
    for (let j = 0; j < height; j += 20) {
      caustics.push({ x: i, y: j, offset: random(TWO_PI) });
    }
  }

  // Create a button to connect to the serial port
  let connectButton = createButton("Connect to Serial");
  connectButton.position(10, 10);
  connectButton.mousePressed(connectToSerial);
}

function draw() {
  background(0, 0, 150); // Deep blue water

  // Draw caustics
  drawCaustics();

  // Draw and update all ripples
  for (let i = ripples.length - 1; i >= 0; i--) {
    let ripple = ripples[i];
    drawRipple(ripple.x, ripple.y, ripple.size);
    ripple.size += 2;

    if (ripple.size > 100) {
      lotuses.push({ x: ripple.x, y: ripple.y, frame: 0 });
      ripples.splice(i, 1);
    }
  }

  // Draw and animate blooming lotuses
  for (let lotus of lotuses) {
    drawBloomingLotus(lotus.x, lotus.y, lotus.frame);
    if (lotus.frame < 30) lotus.frame++;
  }

  // Use sensor values to create ripples
  if (sensorValues.length > 0) {
    for (let r = 0; r < sensorValues.length; r++) {
      for (let c = 0; c < sensorValues[r].length; c++) {
        if (sensorValues[r][c] > 100) {
          let x = map(c, 0, sensorValues[r].length, 0, width);
          let y = map(r, 0, sensorValues.length, 0, height);
          ripples.push({ x, y, size: 10 });
        }
      }
    }
  }
}

async function connectToSerial() {
  // Request a port and open a connection
  try {
    port = await navigator.serial.requestPort();
    await port.open({ baudRate: 9600 });
    serial = port.readable.getReader();

    // Start reading data from the serial port
    readSerial();
  } catch (err) {
    console.error("Error connecting to serial:", err);
  }
}

async function readSerial() {
  while (port.readable) {
    try {
      const { value, done } = await serial.read();
      if (done) {
        serial.releaseLock();
        break;
      }
      // Decode the data and process it
      const data = decoder.decode(value);
      processSerialData(data.trim());
    } catch (err) {
      console.error("Error reading serial data:", err);
      break;
    }
  }
}

function processSerialData(data) {
  // Parse CSV data from Arduino
  let rows = data.split("\n");
  sensorValues = rows.map((row) =>
    row.split(",").map((val) => parseInt(val.trim()))
  );
}

function drawRipple(x, y, size) {
  noFill();
  stroke(255, 255, 255, 100);
  strokeWeight(2);
  ellipse(x, y, size, size);
}

function drawBloomingLotus(x, y, frame) {
  push();
  translate(x, y);

  let scaleAmount = map(frame, 0, 30, 0, 1);
  noStroke();

  fill(255, 182, 193);
  for (let angle = 0; angle < TWO_PI; angle += PI / 6) {
    let petalX = cos(angle) * 30 * scaleAmount;
    let petalY = sin(angle) * 30 * scaleAmount;
    ellipse(petalX, petalY, 20 * scaleAmount);
  }

  fill(255, 105, 180);
  ellipse(0, 0, 20 * scaleAmount);

  pop();
}

function drawCaustics() {
  noStroke();
  fill(255, 255, 255, 30);

  for (let caustic of caustics) {
    let wave = sin(frameCount * 0.05 + caustic.offset) * 5;
    ellipse(caustic.x + wave, caustic.y + wave, 15, 15);
  }
}
const P5SketchWrapper = () => {
  // ... existing state declarations ...

  // Add connection status tracking
  useEffect(() => {
    // Handle tab visibility changes
    const handleVisibilityChange = () => {
      if (document.hidden && isConnected) {
        console.log('Tab hidden, maintaining connection...');
      }
    };

    // Handle before unload
    const handleBeforeUnload = () => {
      if (isConnected) {
        console.log('Page closing, cleaning up serial connection...');
      }
    };

    document.addEventListener('visibilitychange', handleVisibilityChange);
    window.addEventListener('beforeunload', handleBeforeUnload);

    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
      window.removeEventListener('beforeunload', handleBeforeUnload);
    };
  }, [isConnected]);

  const readSerial = async (reader, writer) => {
    try {
      while (true) {
        const { value, done } = await reader.read();
        if (done) {
          console.log('Serial device disconnected normally');
          break;
        }
        
        const text = new TextDecoder().decode(value);
        const values = text.trim().split(',').map(Number);

        if (values.length === 6) {
          setCircleSizes(values);
        }
      }
    } catch (err) {
      if (err.name !== 'NetworkError') {
        console.error('Serial read error:', err);
      }
    } finally {
      setIsConnected(false);
    }
  };

  // ... rest of your component code ...
};