Group Assignment
## Research & Signal Characterization This week, our group focused on probing an input device's analog levels and digital signals. Using a **multimeter** and an **oscilloscope**, we analyzed the response time and noise floors of various sensors. **Key Insights:** I learned that digital signals, particularly from mechanical switches, require debouncing logic because the physical metal contacts "bounce" several times before settling. Probing the analog sensor showed me how much high-frequency noise exists in the environment, which informed my decision to add a stabilization delay in my code. Full group documentation can be found on our **[Lab Group Page](#)**.
## Introduction: The Input Ecosystem Input devices allow microcontrollers to interact with physical reality. For this week's assignment, I explored several categories of sensors defined by their operating principles: * **Buttons & Switches:** Simple mechanical gates that complete a circuit. * **Capacitive:** Measuring proximity through electrostatic field variance. * **Resistive:** Physical force, light, or rotation changing resistance (Potentiometers, LDRs). * **Magnetic:** Using Hall Effect sensors to detect magnetic flux. * **Piezoelectric:** Materials generating voltage from mechanical stress or vibration. ### Data Transmission Protocols * **GPIO (General Purpose I/O):** Binary digital triggers (High/Low). * **GPIO + ADC:** Converting varying analog voltages into digital 10-bit values (0-1023). * **I2C & SPI:** Serial communication for high-speed, complex sensor data like accelerometers. * **Shift Registers:** Serial-to-parallel components used for expanding input capacity.

## Used Sensors Anatomy I used the PCB I made in **Week 8** with **3-pin input headers** (VCC, GND, Signal). Here is the technical breakdown and pin-out explanation for my inventory: ### Obstacle Sensor (IR Reflective) Uses an IR LED transmitter and a photodiode receiver. * **Pins (3 Total):** VCC (3.3V), GND, OUT (Digital). * **Mechanism:** When an object reflects IR light back, the receiver detects it and sends a LOW signal. * **Connection:** Used jumper wires to connect the sensor to the input port ### PIR Motion Sensor A Pyroelectric sensor that measures infrared heat signatures in motion. * **Pins (3-4 Total):** VCC, OUT, GND. * **Mechanism:** The white Fresnel lens (the globe) focuses IR radiation. When motion is detected, the OUT pin toggles HIGH. * **Connection:** Adapted to the 3-pin layout. ### Potentiometer A mechanical rotary variable resistor. * **Pins (3 Total):** Leg 1 (VCC), Leg 2 (Wiper/Signal), Leg 3 (GND). * **Mechanism:** Turning the dial moves the wiper along a resistive track, providing a linear voltage sweep.
3 pin Sensors + the Ultrasonic Sensor (4pin)
3 pin Sensors + the Ultrasonic Sensor (4pin)
2 Pin LDR Sensor
2 Pin LDR Sensor
50K Potentiometer
50K Potentiometer
Speed Sensor
Speed Sensor
Obstacle Sensor
Obstacle Sensor
Ultrasonic Sensor
Ultrasonic Sensor
Motion Sensor
Motion Sensor

## 2. Sensor Family Classification | Sensor | Operating Principle | Family | Signal Category | | :--- | :--- | :--- | :--- | | **PIR (Globe)** | Heat detection | Pyroelectric | **Digital** | | **FC-33 (Horns)** | Beam interruption | Optical | **Digital** | | **Ultrasonic** | Echo timing | Acoustic | **Digital** | | **IR Obstacle** | IR Reflection | Optical | **Digital** | | **Potentiometer** | Resistive track | Electromechanical | **Analog** | | **LDR Sensor** | Photo-resistive | Photo-conductive | **Analog** | | **Tactile Switch** | Circuit closure | Mechanical | **Digital** |

## Individual Assignment & Test Scripts I tested each sensor separately on the PCB J1 Connection port [refer to Week06](week-six.html) for pin connection diagrams. I had to use jumper wires to connect the pins to the PCB relative location, but it was a good exercise to refine my final project PCB. ## Troubleshooting The first issue I faced was I completely mixed up mounting my RP2040 opposite to the schematic as when I did a 3D model visualization of the PCB in week 08, my brain got locked to the model where I mistakenly placed the 3d block opposite to its correct location meaning the Type C port was inverted in the 3D shot... The second issue was my PCB although equipped with 3 pin PCB outlets for potential inputs, my PCB was in this order
3 Pin mismatch
Although for this week the workaround was using jumper wires and mediators, a long term lesson learned is to either ensure the PCB is for a specific range of inputs , or predesign for the expected inputs if no wiring is favored. But also I got to know the existence of "Interposer" Cables , which can be manipulated to cross match the pins.
### Potentiometer In the potentiometer test , I used two code versions, the first one below reflected too much noise and the curves were not "smooth" so with search and AI "vibe coding", I learned how to smooth out the plotted curves
smooth_curve.ino

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C

// --- MUX CONTROL ---
const int S0 = 0; const int S1 = 1; const int S2 = 2; const int S3 = 3;
const int SIG_PIN = 26; 

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// --- SMOOTHING VARIABLES ---
float smoothedValue = 0;
float alpha = 0.15; // Smoothing factor (0.01 = very slow/smooth, 0.9 = jerky/fast)
int history[SCREEN_WIDTH];
unsigned long lastPlotTime = 0;
const int plotInterval = 39; // 128 pixels * 39ms ≈ 5 seconds of total screen width

void setup() {
  Wire.setSDA(6); Wire.setSCL(7); Wire.begin();
  pinMode(S0, OUTPUT); pinMode(S1, OUTPUT);
  pinMode(S2, OUTPUT); pinMode(S3, OUTPUT);
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) for(;;);
  
  display.clearDisplay();
  for(int i=0; i < SCREEN_WIDTH; i++) history[i] = SCREEN_HEIGHT - 1;
}

void loop() {
  // 1. Select J1
  digitalWrite(S0, LOW); digitalWrite(S1, LOW);
  digitalWrite(S2, LOW); digitalWrite(S3, LOW);
  
  // 2. Read and Smooth (The "Mario" physics)
  int raw = analogRead(SIG_PIN);
  // Exponential Moving Average Formula
  smoothedValue = (alpha * raw) + ((1.0 - alpha) * smoothedValue);

  // 3. Timing Logic (Plotting at the speed you requested)
  if (millis() - lastPlotTime >= plotInterval) {
    lastPlotTime = millis();

    // Shift history left
    for (int i = 0; i < SCREEN_WIDTH - 1; i++) {
      history[i] = history[i + 1];
    }
    // Add new smoothed point to the end
    history[SCREEN_WIDTH - 1] = map((int)smoothedValue, 0, 1023, SCREEN_HEIGHT - 1, 15);
  }

  // 4. Draw
  display.clearDisplay();
  
  // HUD
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.print("SMOOTHED V: "); 
  display.print((smoothedValue / 1023.0) * 3.3, 2);

  // Draw the smooth curve
  for (int i = 0; i < SCREEN_WIDTH - 1; i++) {
    display.drawLine(i, history[i], i+1, history[i+1], SSD1306_WHITE);
  }

  display.display();
}
    
potentiometer_test.ino

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C

const int S0 = 0, S1 = 1, S2 = 2, S3 = 3;
const int SIG_PIN = 26;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

int waveBuffer[SCREEN_WIDTH];

void setup() {
  Wire.setSDA(6);
  Wire.setSCL(7);
  Wire.begin();

  pinMode(S0, OUTPUT); pinMode(S1, OUTPUT);
  pinMode(S2, OUTPUT); pinMode(S3, OUTPUT);
  analogReadResolution(10); 

  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) for(;;);
  
  display.clearDisplay();
  for(int i=0; i<SCREEN_WIDTH; i++) waveBuffer[i] = SCREEN_HEIGHT / 2;
}

void selectMux(int channel) {
  digitalWrite(S0, (channel & 0x01));
  digitalWrite(S1, (channel & 0x02));
  digitalWrite(S2, (channel & 0x04));
  digitalWrite(S3, (channel & 0x08));
}

void loop() {
  selectMux(0);
  int rawVal = analogRead(SIG_PIN);
  int yMapped = map(rawVal, 0, 1023, SCREEN_HEIGHT - 1, 0);

  for (int i = 0; i < SCREEN_WIDTH - 1; i++) {
    waveBuffer[i] = waveBuffer[i + 1];
  }
  waveBuffer[SCREEN_WIDTH - 1] = yMapped;

  display.clearDisplay();
  display.drawFastHLine(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SSD1306_WHITE);
  
  for (int x = 1; x < SCREEN_WIDTH; x++) {
    display.drawLine(x - 1, waveBuffer[x - 1], x, waveBuffer[x], SSD1306_WHITE);
  }

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.print("J1 LIVE: ");
  display.print(rawVal);
  display.display();
}
    
### Obstacle Sensor
obstacle_detector.ino
```cpp /* --- CONFIGURATION (Change these to match your PCB) --- */ const int S0_PIN = 16; const int S1_PIN = 17; const int S2_PIN = 18; // The 'COM' or 'SIG' pin on the MUX connected to RP2040 const int MUX_IN_PIN = 26; // The channel your IR sensor is plugged into (0-7) const int SENSOR_CHANNEL = 2; void setup() { Serial.begin(115200); // Set MUX selection pins as outputs pinMode(S0_PIN, OUTPUT); pinMode(S1_PIN, OUTPUT); pinMode(S2_PIN, OUTPUT); // Set MUX signal pin as input pinMode(MUX_IN_PIN, INPUT); Serial.println("--- IR Sensor MUX Test Starting ---"); } void selectMuxChannel(int channel) { // Write the binary address to the selection pins digitalWrite(S0_PIN, channel & 0x01); digitalWrite(S1_PIN, (channel >> 1) & 0x01); digitalWrite(S2_PIN, (channel >> 2) & 0x01); } void loop() { // 1. Select the correct MUX channel selectMuxChannel(SENSOR_CHANNEL); // 2. Small delay for the signal to stabilize delayMicroseconds(50); // 3. Read the sensor (Digital signal) int sensorState = digitalRead(MUX_IN_PIN); // IR modules usually output LOW (0) when an obstacle is detected if (sensorState == LOW) { Serial.print("Channel "); Serial.print(SENSOR_CHANNEL); Serial.println(": OBSTACLE DETECTED!"); } else { Serial.print("Channel "); Serial.print(SENSOR_CHANNEL); Serial.println(": Clear"); } delay(200); // Wait 200ms before next read }
### Motion Detector
Motion_test.ino
```cpp /* --- CONFIGURATION --- */ const int S_PINS[] = {16, 17, 18, 19}; // S0, S1, S2, S3 const int MUX_IN_PIN = 26; // Timing variables unsigned long lastUpdate = 0; const int scanInterval = 100; // ms void setup() { Serial.begin(115200); for (int i = 0; i < 4; i++) { pinMode(S_PINS[i], OUTPUT); } pinMode(MUX_IN_PIN, INPUT_PULLUP); } void selectChannel(int channel) { for (int i = 0; i < 4; i++) { digitalWrite(S_PINS[i], (channel >> i) & 0x01); } delayMicroseconds(10); } void loop() { if (millis() - lastUpdate >= scanInterval) { // This array controls which channels appear in your output int channelsToScan[] = {0, 2}; for (int ch : channelsToScan) { selectChannel(ch); int state = digitalRead(MUX_IN_PIN); // Formatting the output string Serial.print("CH"); Serial.print(ch); Serial.print(": "); Serial.print(state == LOW ? "[ TRIGGERED ] " : "[ Clear ] "); } Serial.println(); // Moves to next line after all channels are printed lastUpdate = millis(); } }
| Sensor Type | Test Objective | Key Function/Logic | Output Method | | :--- | :--- | :--- | :--- | | **Potentiometer** | Analog Mapping & Graphing | `map(val, 0, 1023, 0, 40)` | SSD1306 OLED (Bar Graph) | | **Obstacle** | Threshold Verification | `digitalRead()` | Serial Plotter (Binary Waveform) | | **Motion** | Multi-input Logic | `for (int ch : channelsToScan)` | Serial Monitor (Status String) |
## Results

Results of all sensors connected to the PCB.

### Arduino IDE Setup & Configuration I utilized the **Arduino IDE 2.3.2** as my main diagnostic interface. To ensure the multiplexer could cycle through 7 sensors fast enough for the human eye to see as "simultaneous," I configured the baud rate to **115200**. ### Tools Used: * **Serial Monitor:** For verifying raw binary switching logic. * **Serial Plotter:** For visualizing waves and pulses.

## Graphing Strategy: Waveform Distinction In the documentation results, I distinguish sensors by their wave signatures: * **Sinusoidal/Smooth Waves:** Produced by the Potentiometer (manual dial) * **Square/Step Waves:** Produced by the PIR, Obstacle sensor, and Switch.
Pro Tip / Warning

One thing I realized while working on this is how important the map() function actually is. It’s basically translating “sensor language” (0–1023) into “screen language” (63–0). Without it, something like a value of 512 would just end up off-screen since the display is only 64 pixels tall. Using map() fixes that by scaling everything perfectly to fit the display height.

I also relied on the waveBuffer array to create the scrolling waveform effect instead of just a jumping dot. By shifting values (waveBuffer[i] = waveBuffer[i + 1]), I’m essentially keeping a history of previous readings. There’s no complex math here—it’s really just simple data handling to simulate motion.

Finally, using display.drawLine() made a big difference in how the output looks. If I used drawPixel() alone, the result would be a bunch of disconnected dots, especially when the potentiometer moves quickly. Connecting points with drawLine() keeps everything smooth and continuous, giving it that clean waveform look.



Resources & Assets

## Global Lecture Takeaways (documentation in progress)