Summer's double transducer: magnetic field to force.
Yutong's double transducer: magnetic field to force.
Summer: Magnetic input with magnetometer.
Summer: Thermistor detects bulb brightness.
Summer: Close up of the output force. Using a straw attached to the motor to push the coconut open.
Yutong: Close-up of the soldered middle step: thermistor detecting the warmth of the lightbulb.
Yutong: Close-up of the skip-the-middle button.
Yutong: Magnetic field input with a elevated magetometer.
Yutong: Straw attached to the servo motor to output force.
Summer's final outcome.
Yutong's final
Starting from the left side of the board, we have a magnetometer that measures the strength and direction of a magnetic field along three axes: X, Y, and Z. In our case we only use one of the axis just for the sake of simplicity. In the middle we use the input of magnetic field to power a lightbulb, and take the temperature of the bulb to power a motor. We attached a straw on the motor to apply springy force.
As a magnet is moving closer to a magnetometer, the bulb will increase brightness and temperature will also increase. When the temperature goes higher, the degree of motor will increase, so that it looks like the straw is being pressed further. When the magnet is removed, bulb won't lit up anymore. As the temperature goes down, motor degree will decrease and the straw won't be pressed as much.
Yutong: I had to resolder the board after realizing that I had used the wrong resistor for the thermistor. I used a 100k Ohms resistor when the thermistor asked for a 10k Ohms resistor.
Yutong: After mapping the temperature value to the degrees of rotation on the servo motor, I realized that the placement of the servo motor does not really satisfy the prompt of exerting force. I ended up rotating the servo motor to sit sideways, so that when it rotates, the straw can apply force to the table.
Summer: I also have to re-solder the board because the bulb won't lit up anymore. It wasn't the code that had issue because the bulb stopped working very suddenly. Maybe I cooked something on the board by accident?
Summer: After I connected everything, I realized I understood the prompt wrong. Because it's supposed to gradually increase brightness and motor instead of a binary on/off interaction. So I had to map the input differently.
Yutong:
This project was my first hands-on experience with hardware and coding in C++ after the basic async lectures. With a lack of experience, I spent much longer than anticipated to figure out not only the technical aspects but also an effective workflow. During the process, I felt that I grew both creatively and technically. At first, I approached it as a straightforward wiring and coding challenge: I thought I would just need to put together the wiring according to my ideation schematics and write code to map individual inputs to their outputs. As I moved from concept to implementation, I began to see the layered complexity of the project. Bringing together the magnetometer, thermistor, incandescent bulb, and servo demanded that I consider not only the individual components but also how they would interact to produce a smooth output.
The process of getting each step to smoothly transition to the next with the right timing, reasonable value, reasonable placement, and accurately representing them on the LCD screen was incredibly time-intensive. For example, tuning the mapping and constraining the sensor values to produce a servo movement that can smoothly rotate to apply force to the table required far more experimentation than I expected. Debugging small inconsistencies, such as the LCD occasionally showing expected four-digit numbers, taught me how crucial it is to understand the code thoroughly rather than assuming where the errors came from. These moments of friction were frustrating, but ultimately deepened my understanding of hardware behavior and data normalization.
Looking back, I realized the importance of building slow and steady from the ground up. In the future, I hope to connect and test one input/output at a time. That way, I can easily isolate problems when things stop working, allowing me to directly locate the layer of build that the bug is hiding in. An incremental, structured approach would've saved hours of frustration. Going forward, I want to take this mindset into more complex interactive builds, so that even as projects grow in complexity, their foundations remain solid and their debugging stays manageable.
Summer:
The biggest challenge for me was connecting all the inputs together and having the patience to debug. I started the project by building small parts separately, and everything seemed to work fine at first. But once I combined all the components, things got tricky, and that was where I spent the most time troubleshooting.
The middle stage was especially challenging because I wasn’t very confident with soldering, and adding a transistor introduced another layer of complexity. At first, I wired the transistor incorrectly, and it became piping hot with a strange smell, which definitely freaked me out. But that mistake helped me understand how transistors actually work, and now I feel much more comfortable using them. I’m glad I learned about them, especially since many of the tools I’m interested in, like fans, rely on transistors.
Overall, I learned a lot about both hardware and software. I’m used to debugging code, so software issues weren’t too intimidating. But in physical computing, debugging can happen on both the hardware and software sides, which can be frustrating but also rewarding. There are so many possible points of failure, and figuring out the root cause is surprisingly satisfying. I remember thinking a wire I soldered was broken, so I redid it, only to later realize the issue was in the code.
Not allowing AI for the first project turned out to be a great idea. I wouldn’t say I’m fully confident in coding yet, but writing the code myself helped me actually understand how it works. It’s reassuring to know what’s going on before relying on AI tools. Otherwise, I feel like I wouldn’t have learned as much.
Having a concept or theme also really motivates me because it helps make my design more intentional and meaningful. That part always comes naturally for me, and I’m happy with my final outcome.
Looking ahead, I want to explore more types of inputs and outputs to build confidence in creating things independently. When the course started, I knew nothing about hardware or Arduino, so being able to complete a project like this in just a few weeks feels incredible. My ultimate goal is not to let technical limitations stop me from making something I imagine. Now, when I think about new ideas, I can start visualizing which tools or components to use, and that feels exciting. It’s empowering to think about design and technology together, not one after the other.
/*
Double transducer: magnetic field to motor force
We use a magnetometer to take magnetic field value and convert it into bulb brightness, then take temperature of the bulb brightness. As the temperature increases, the motor rotation degrees will increase. If temperature goes down, the motor rotation degrees will decrease. LCD screens show input of y value, bulb brightness, temperature, and motor rotation degrees.
Pin mapping:
Arduino pin | role | details
------------------------------
4 output Servo Motor Pin
6 output Bulb Pin
A3 input reads temperature
A1 input reads brightness
12 input reads button
Resources I used:
1. Set up magnetometer: https://www.circuitbasics.com/how-to-setup-a-magnetometer-on-the-arduino/
2. Set up servo motor: https://www.instructables.com/Arduino-Servo-Motors/
3. Clear and print LCD value: https://www.reddit.com/r/arduino/comments/1gr8ige/how_to_clear_only_the_top_line_of_a_16x2_lcd/
*/
#include <Servo.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <QMC5883L.h>
// Thermistor settings
int ThermistorPin = A3;
const int buttonPin = 12; // the number of the pushbutton pin
int buttonState = 0; // variable for reading the pushbutton status
// LCD screen setup
LiquidCrystal_I2C screen(0x27, 16, 2);
// Servo motor setup
const int doormotorPin = 4;
Servo Servo1;
QMC5883L compass;
// Bulb setup
const int bulbPin = 6;
void setup() {
Wire.begin();
Serial.begin(9600);
compass.init();
Servo1.attach(doormotorPin);
screen.init();
screen.backlight();
pinMode(bulbPin, OUTPUT);
pinMode(ThermistorPin, INPUT);
pinMode(buttonPin, INPUT);
}
// this is to clear the LCD screen
void clearAndPrint(int col, int row, int numChars, int value) {
screen.setCursor(col, row);
for (int i = 0; i < numChars; i++) {
screen.print(" ");
}
screen.setCursor(col, row);
screen.print(value);
}
void loop() {
int x, y, z;
compass.read(&x, &y, &z);
// Simplified formula to only use the Y-axis value
float magnitude = abs(y);
// Map magnitude to a 0-99 range for consistency
int mappedMagnitude = map(magnitude, 3000, 27000, 0, 99);
mappedMagnitude = constrain(mappedMagnitude, 0, 99);
// Map the new 0-99 range to the full 0-255 for the bulb's PWM control
int brightness = map(mappedMagnitude, 0, 99, 0, 255);
// Read temperature from thermistor
int Vo = analogRead(ThermistorPin);
// read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);
// A variable to store the servo's current intended position
int currentServoPosition = 0;
// Main logic to control all components based on the button state
if (buttonState == HIGH) {
// If button is pressed, the bulb is OFF and motor's position is proportional to magnetic field
analogWrite(bulbPin, 0);
// Motor position is proportional to magnetic field (Y-axis)
int targetServoPosition = map(magnitude, 3000, 27000, 80, 150);
currentServoPosition = constrain(targetServoPosition, 80, 150);
} else {
// If button is NOT pressed, bulb and motor are active under their normal conditions
analogWrite(bulbPin, brightness);
// Motor position is proportional to temperature
int targetServoPosition = map(Vo, 490, 550, 80, 150);
currentServoPosition = constrain(targetServoPosition, 80, 150);
}
// Write the calculated position to the servo
Servo1.write(currentServoPosition);
// Display values on LCD
screen.setCursor(0, 0);
screen.print("i:");
clearAndPrint(2, 0, 2, mappedMagnitude);
screen.setCursor(5, 0);
screen.print("m:");
clearAndPrint(7, 0, 3, map(brightness, 0, 255, 0, 99));
screen.setCursor(5, 1);
clearAndPrint(5, 1, 3, map(Vo, 480, 550, 0, 99));
screen.setCursor(11, 1);
screen.print("o:");
clearAndPrint(13, 1, 3, map(currentServoPosition, 80, 150, 0, 99));
// Print raw XYZ and other values to Serial Monitor
Serial.print("Magnitude: ");
Serial.println(magnitude);
Serial.print("X: ");
Serial.println(x);
Serial.print("Y: ");
Serial.println(y);
Serial.print("Z: ");
Serial.println(z);
Serial.println(" ");
Serial.println(Vo);
delay(50);
}