Video Clip

Summary Slide

presentation

Principles and Practices

  • Plan and sketch a potential final project

This is where I will start defining my final project idea and start to get used to the documentation process.


Initial Project Idea - Week 1

As my final project for Fab Academy, I'm designing my own personal versatile angle-adjustable lap table specifically. The surface is designed in such a way that has steppers for the laptop to be angle as desired and most comfortable. During prolonged use, an integrated cooling system beneath the surface will help maintain acceptable safe temperatures for the laptop. This will be done using a microcontroller, most likely ESP32-S3, connected to a temperature sensor (like LM35-input device). The sensor will sense when temperature of the laptop's CPU goes above a certain threshold (for heavy load: 80°C)and send a warning to an embedded capacitive touch screen to turn on the fan. A separate tablet holder for increased productivity and integrated USB ports for device charging will be also parts of the design. My laptop measures 360mm (length) × 240mm (width). The table measures 800mm (length) × 400mm (width). For ergonomic support, there is an 8 cm wrist cushion included. The project's goal is to provide me with a work environment solution by integrating comfort, technology, and utility, especially in the days where I don't feel like going out of my bed, yet still wanting to be productive and completing my tasks.

Rough Sketch for Project Idea

Project Sketch

Initial Research

Research on Hardware possibility - Week 3

A good source to acquire datasheets from: alldatasheet

1. Temperature Monitoring and Control:
Temperature Monitoring and Control:

Create firmware using the Arduino IDE that utilizes an ADC channel to read the LM35 sensor. Since the ESP32 has a 12-bit ADC (4096 levels) and typically operates with Vref = 3.3V, the raw ADC reading needs to be converted to a meaningful temperature value.

However, due to the ESP32's non-linear ADC response, the LM35 sensor may require a voltage offset correction. A diode-based offset circuit (e.g., two diodes in series) is recommended to shift the sensor output voltage into a more linear ADC range.

Corrected Temperature Formula:
Temperature (°C) = ((ADC_Value * Vref) / ADC_Max - V_offset) * 100

Where:

  • Vref = 3.3V (ESP32 reference voltage)
  • ADC_Max = 4095> (12-bit ADC resolution)
  • V_offset ≈ 1.4V (using two diodes in series, if required for correction)

source:

LM35 Sensor Interfacing with ESP32 - Electronic Wings

Set a threshold temperature (resource: Normal CPU Temperature); if the measured temperature exceeds this threshold, adjust the PWM duty cycle to increase fan speed automatically and display notification on the display.

Control PWM fan

Create PWM signals for every fan using the ESP32's PWM capabilities.

To optimize cooling, map the temperature data to a PWM duty cycle. For instance, if the temperature is extremely high, set the duty cycle close to 100%.

Potential Hardware Components

Research on PCB Production - Week 6

For this week, working on KiCad for the first time was very frustrating. First, I started sketching the schematic in order for the PCB to be produced. I read through the datasheets of all the possible components. I learned that a MOSFET is most likely needed. A MOSFET is a shortcut for Metal Oxide Semiconductor Field-effect Transistor (MOSFET, MOS-FET, or MOS FET), which is N−channel transistor with an insulated gate where the voltage regulates the conductivity of the device. Hence, it will be used as a low-side switch to drive the fans (its gate driven by a Pico GPIO, drain connected to the FAN, and source to GND). Moreover, to protect against inductive voltage spikes when the fans are turned off, a Diode is added. A diode is Diodes have polarity, determined by an anode (positive lead) and cathode (negative lead) and it mostly allow current to flow only when positive voltage is applied to the anode.

Stress Test Laptop's CPU

Using Prime95, I conduct a stress test for the CPU to determine how hot my laptop gets. A stress test is a process that deliberately pushes the computer system beyond its normal operational capacity to evaluate its stability and identify its weaknesses. To monitor CPU temperatures during the stress test, I installed HWMonitor This will help me determine more precisely what kind of fans I will eventually need and where to mount the temperature sensors for the most accurate temperature reading when the laptop is stressed while using multiple software simultaneously that require high CPU performance. From the Prime95 options, I selected torture test and I ticked on Small FFTs to push the laptop to the limits. Then, Pressed ok and ran the test. I switch to HWMonitor window to monitor the temperature. I found out that P-cores (performance Cores) max temperature to be 69°C, and the E-Cores (Efficiency Cores) Max Temperature to be 66°C. This means that my laptop's CPU is still in the safe zone; remaining under 70°C. However, the heat on the bottom surface is still visible and working with software such as Fusion360 requires constant monitoring and controlling to keep temperature under 70°C and to prevent thermal throttling to maintain standard performance.
Prime95
HWMonitor
Furthermore, I will try to do a paid benchmark test for the overall state of the laptop under stress more accurately. Moreover, I heard there is a thermal camera in our fablab that I could use to capture visually where the hot spots of my laptop surface. I will need to investigate more about that

Exploring Unified Power Supply with Voltage Regulation

When designing an electronic system with multiple voltage requirements, such as powering a microcontroller, temperature sensors, DC cooling fans, and OLED display,it is essential to use an efficient power distribution method. One common approach is using a single power supply with a voltage regulator to provide multiple output voltages (e.g., 12V for fans, 5V for the microcontroller, and 3.3V for sensors). This method ensures a compact, efficient, and cost-effective setup, decreasing the need for several power sources and avoiding complex wiring. However, different components in the system require different voltages. For instance:
  • 12V → Required for the fans.
  • 5V → Used for ESP32 or Raspberry Pi Pico microcontrollers.
  • 3.3V → Needed for sensors, and the display.

How a Voltage Regulator Works

The most commonly used regulators include:

  • Linear Voltage Regulators (e.g., LM317, 7805, 1117-3.3V): simple mechanism where they use a transistor controlled by a negative-feedback circuit to produce a specified output voltage, but less efficient as they dissipate excess energy as heat.
  • Switching Voltage Regulators (e.g., Buck Converters, LM2596 ): consist of a power switch, an inductor, and a diode to transfer energy from input to output. They are highly efficient since they convert energy with minimal heat loss. This is usually done by storing and releasing energy through an inductor.

Example Circuit Design

A power distribution system using a single 12V power adapter and voltage regulators: (See Link)
    12V Power Input
  • 12V DC Fan (Direct Connection)
  • Step-Down Regulator LM2596 (Buck Converter) → 5V (Microcontroller and sensors)
  • From the Pi Pico W → 3.3V (OLED display)

2D and 3D Modeling

Designing LapTable Prototype for CNCing - Week 7

Table Prototype
For this week, I designed a prototype for my table. Since I am not decided yet on the hardware components, I designed the table without any pockets for the electronics to be inserted in. However, for the adjusting angle mechanism, I designed rod and slots for the rod to go in and I added Revolute joints to simulate the real movement of this part. For the pivot point, I designed for now just a simple wooden dowel, since the challenge for the week was to avoid using screws or glue. In order to sync the movement of the rod with the movement of the table middle section surface, I used Motion Link. Unfortunately, this week was full of unpredictable incidents that hindered the cutting process. Hence, the production of the prototype is still to be continued!

Electronics Production - Week 8

For this week, I produced the PCB that I am planning to implement in my final project. As a microcontroller I am using the Raspberry Pi Pico:. I added few extra pinheaders in case I decide later to integrate few hardware components onto my PCB. Furthermore, I soldered the capacitors that will be necessary to regulate the voltage using Linear Voltage Regulator DPAK. This will ensure providing stable power to the fan (12V), as well as the microcontroller (5V). However, I still need to procure the temperature sensors LM35 temperature sensor, the MOSFET and the diode for the PWM control of the fan. Moreover, the fan and the OLED display still have to be ordered in order to test the functionality of the circuit as a first step before adding any modifications onto it.
PCBsoldered

PCB Soldered and Tested - Week 9

Input Device

DS18B20 Sensor Test with my personal PCB

I linked the DS18b20 to pinheaders I soldered on the board I produced in Week8- Electronics Production. One pin is connected to common GND, one pin is connected to 5V, and the third one is connected to GPIO 27 (output). In Arduino IDE, I ran the code example single from the library and I read the temperature on the serial monitor in Celsius degrees.I figured it was roughly around room temperature (~24°C).
SerialMonitorDS18B20

Stress and Benchmark Test

However, in order to push the laptop to its limit, Using Prime95, I conduct a stress test for the CPU to determine how hot my laptop gets. A stress test is a process that deliberately pushes the computer system beyond its normal operational capacity to evaluate its stability and identify its weaknesses. This will help me determine more precisely what kind of fans I will eventually need and where to mount the temperature sensors for the most accurate temperature reading when the laptop is stressed while using multiple software simultaneously that require high CPU performance. From the Prime95 options, I selected torture test and I ticked on Small FFTs to push the laptop to the limits. Then, Pressed ok and ran the test. I repeated the same process but with a throughput benchmark test.
TortureTest2
Benchmarktest
To amplify even more, I placed my setup under the sun and fixated the temperature sensor on the laser cutter under the laptop, where the CPU is placed (middle upper section of the laptop's bottom surface)
Laptopundersun
Laptopundersun2
Reading the temperature on the Serial Monitor, I noticed how it raised up to 33°C. This means that the sensor in fact detected the impact of the sun, as well as the stress and benchmark test on the heat of the laptop's CPU.
Temperaturesensorunderlaptop

Output Device - Week 10

Testing with DC 12V fan and the Voltage Regulator

To verify the voltage regulator is soldered properly and regulates the desired voltages: one leg has 12V coming in and the other leg has 5V going out, a multimeter was used.
It worked! :) We can see how our fan makes the receipt paper fly!

Progress on Final Arduino Code - Week 11

Testing the Fan's Threshold for PWM Duty Cycle

To provide the 12 v that the fan requires, I used the powerbench and connected it to the terminal connector dedicated for it in my designed PCB (see Week 6 - Electronics Design) In order to test what is the minimum Threshold the fan can detect for PWM and find out the right mapping range, the example AnalogInOutSerial was used. Since only the analogOutPin is our interest, I changed this to pin 16, which is the pin where the MOSFET-fan circuit are connected. Then, I varied the outputValue from 80 all the way to 230 to check when the fan starts to respond. The result was the minimum threshold the fan can respond for PWM was 230. My final code was modified accordingly.


                                    /*
  Analog input, analog output, serial output - Modified

  Reads an analog input pin, maps the result to a range from 0 to 255 and uses
  the result to set the pulse width modulation (PWM) of an output pin.
  Also prints the results to the Serial Monitor.

  The circuit:
  - potentiometer connected to analog pin 0.
    Center pin of the potentiometer goes to the analog pin.
    side pins of the potentiometer go to +5V and ground
  - LED connected from digital pin 9 to ground through 220 ohm resistor

  created 29 Dec. 2008
  modified 9 Apr 2012
  by Tom Igoe

  This example code is in the public domain.

  https://docs.arduino.cc/built-in-examples/analog/AnalogInOutSerial/
*/

// These constants won't change. They're used to give names to the pins used:
const int analogInPin = A1;  // Analog input pin that the potentiometer is attached to
const int analogOutPin = 16;  // Analog output pin that the MOSFET is attached to

int sensorValue = 0;  // value read from the pot
int outputValue = 0;  // value output to the PWM (analog out)

void setup() {
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
}

void loop() {
  // read the analog in value:
  sensorValue = analogRead(analogInPin);
  // map it to the range of the analog out:
 // outputValue = map(sensorValue, 0, 1023, 0, 255);
  outputValue =230; // to test the Fan's threshold for PWM 

  // change the analog out value:
  analogWrite(analogOutPin, outputValue);

  // print the results to the Serial Monitor:
  Serial.print("sensor = ");
  Serial.print(sensorValue);
  Serial.print("\t output = ");
  Serial.println(outputValue);

  // wait 2 milliseconds before the next loop for the analog-to-digital
  // converter to settle after the last reading:
  delay(2);
}


Final Code Draft Explanation

First, finding the most accurately calibrated library for the DS18B20 temperature sensor reading was necessary. DallasTemperature library. I downloaded the library and integrated it to my code. However, when trying to increase the temperature with body temperature by holding the sensor, the sensor was responding very slowly and the temperature raised up to only 27 °C. Then, We tried out few more libraries for the DS18B20 temperature sensor and most of them had compatibility issues with Pi Pico W. Finally, reading through this link, we figured that microDS18B20 library was a referred to as a more accurate updated library. Therefore, I went and downloaded the library from the library manager.
microDSBTemp
To test the library's compatibility with Pi Pico W, we tried out one pin one sensor example. Finally, it worked and the temperature sensing precision was improved.
Using the OneWire protocol and the microDS18B20 library for the temperature sensors, this code measures the temperatures of two DS18B20 sensors and averages them to operate a DC fan and light up an LED. With defining spacing for clarity, it sets up the default I2C bus to activate an OLED display that shows the fan status and mean temperature. It requests both sensors for their temperature measurements, calculates the mean, and outputs the results to the Serial Monitor. In order to control the fan status, a temperature threshold is set to 23°C. If the average temperature is below 23°C, the fan stays off; when the temperature rises to 23°C, the fan starts at 20% speed. The fan speed rises by an extra 20% for each 1°C increment above 23°C (i.e., it becomes 40% at 24°C, 60% at 25°C, 80% at 26°C, and 100% at 27°C or higher). Additionally, based on the previous test findings, the PWM output is mapped to the range of 230 to 255. This ensures that even at the lower end of the speed range, there is sufficient voltage/current to properly start the DC fan. the OLED display, the mean temperature is always displayed on the first line. on the second line, the fan status is displayed; if the fan is off, it shows just Fan:OFF. However, if the fan is on; it shows for the first 2 seconds Fan:ON, then it disappears and Speed:(value) % appears according to the mapping in the code!

                                            /*
                                            The following code is a draft for my final project:
                                            - Drive a fan based on the mean temperature recorded by two DS18B20 temperature sensors 
                                            - To control the speed of the fan, it is mapped to the PWM range of 230 to 255, where a minimum temperature threshold is set and an increment of 20% speed increase corresponds to 1°C temperature increased
                                            - Information about fan status and mean temperature is shown on an OLED01.3 display 
                                          */
                                            
                                                #include 
                                                #include 
                                                #include 
                                                #include 
                                                
                                                // I2C OLED Setup 
                                                #define I2C_ADDRESS 0x3C  
                                                #define SCREEN_WIDTH 128
                                                #define SCREEN_HEIGHT 64
                                                
                                                // Initialize the OLED display using the default I2C bus (Wire)
                                                Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
                                                
                                                // DS18B20 Temperature Sensors 
                                                // Sensors are connected to GPIO26 and GPIO27
                                                MicroDS18B20<26> sensor1;
                                                MicroDS18B20<27> sensor2;
                                                
                                                // LED and Fan Definitions 
                                                #define LED_PIN 22   // LED to indicate temperature threshold
                                                #define FAN_PIN 16   // DC fan controlled via PWM
                                                
                                                // Temperature thresholds for fan control
                                                const float tempThreshold = 22.0;  // Fan starts running at 22°C
                                                const float maxTempForFan = 26.0;    // Fan reaches 100% speed at 26°C
                                                
                                                // Global variable to track when the fan turns on (in milliseconds)
                                                unsigned long fanOnStartTime = 0;
                                                
                                                void setup() {
                                                  Serial.begin(9600);
                                                
                                                  // Initialize I2C bus
                                                  Wire.begin();
                                                
                                                  // Initialize the OLED display; if not found, pause execution
                                                  if (!display.begin(I2C_ADDRESS, true)) {  
                                                    Serial.println(F("OLED not found"));
                                                    while (1);
                                                  }
                                                
                                                  // Configure the LED and Fan pins as outputs
                                                  pinMode(LED_PIN, OUTPUT);
                                                  pinMode(FAN_PIN, OUTPUT);
                                                
                                                  // Display a startup message
                                                  display.clearDisplay();
                                                  display.setTextSize(2);
                                                  display.setTextColor(SH110X_WHITE);
                                                  display.setCursor(0, 10);
                                                  display.println("Starting...");
                                                  display.display();
                                                  delay(2000);
                                                }
                                                
                                                void loop() {
                                                  // Request temperature readings from both sensors
                                                  sensor1.requestTemp();
                                                  sensor2.requestTemp();
                                                  
                                                  // Wait for conversion.
                                                  delay(1000);
                                                  
                                                  // Variables to hold temperature values and sensor status
                                                  float temp1 = 0, temp2 = 0;
                                                  bool sensor1_ok = false, sensor2_ok = false;
                                                  
                                                  // Read sensor1
                                                  if (sensor1.readTemp()) {
                                                    temp1 = sensor1.getTemp();
                                                    sensor1_ok = true;
                                                  } else {
                                                    Serial.println("Error reading sensor1");
                                                  }
                                                  
                                                  // Read sensor2
                                                  if (sensor2.readTemp()) {
                                                    temp2 = sensor2.getTemp();
                                                    sensor2_ok = true;
                                                  } else {
                                                    Serial.println("Error reading sensor2");
                                                  }
                                                  
                                                  // Validate sensor data
                                                  bool validData = sensor1_ok && sensor2_ok;
                                                  float meanTemp = 0;
                                                  
                                                  if (validData) {
                                                    meanTemp = (temp1 + temp2) / 2.0;
                                                    Serial.print("t1: ");
                                                    Serial.print(temp1);
                                                    Serial.print(" C, t2: ");
                                                    Serial.print(temp2);
                                                    Serial.print(" C, Mean: ");
                                                    Serial.print(meanTemp);
                                                    Serial.println(" C");
                                                  } else {
                                                    Serial.println("Cannot compute mean: one or more sensor readings failed");
                                                  }
                                                  
                                                  // Variables for fan control
                                                  bool fanOn = false;
                                                  int fanSpeedPercent = 0;
                                                  int pwmValue = 0;  // PWM value (0 - 255)
                                                
                                                  // Only control the fan if sensor data is valid
                                                  if (validData && (meanTemp >= tempThreshold)) {
                                                    digitalWrite(LED_PIN, HIGH);  // Turn on LED
                                                
                                                    // Calculate fan speed percentage based on temperature between threshold and maxTempForFan
                                                    if (meanTemp >= maxTempForFan) {
                                                      fanSpeedPercent = 100;
                                                    } else {
                                                      fanSpeedPercent = (int)(((meanTemp - tempThreshold) / (maxTempForFan - tempThreshold)) * 100);
                                                    }
                                                    
                                                    // Map the fan speed percentage to PWM value in the range 170 to 255
                                                    pwmValue = map(fanSpeedPercent, 0, 100, 170, 255);
                                                    analogWrite(FAN_PIN, pwmValue);
                                                    fanOn = true;
                                                    
                                                    // Record fan activation time if it just started
                                                    if (fanOnStartTime == 0) {
                                                      fanOnStartTime = millis();
                                                    }
                                                  } else {
                                                    digitalWrite(LED_PIN, LOW);   // Turn off LED
                                                    analogWrite(FAN_PIN, 0);        // Turn off fan
                                                    fanSpeedPercent = 0;
                                                    fanOnStartTime = 0;
                                                  }
                                                  
                                                  // Update the OLED display
                                                  display.clearDisplay();
                                                  display.setTextSize(2);
                                                  display.setCursor(0, 0);
                                                  
                                                  if (validData) {
                                                    // First line: Mean temperature with unit Celcius degrees
                                                    display.print("Mean:");
                                                    display.print(meanTemp, 1);
                                                    display.print("C");
                                                  
                                                    // Second line: Fan status/speed
                                                    display.setTextSize(2);
                                                    display.setCursor(0, 35);
                                                    if (fanOn) {
                                                      // For the first 2 seconds, display "Fan: ON"
                                                      if (millis() - fanOnStartTime < 2000) {
                                                        display.println("Fan: ON");
                                                      } else {
                                                        display.print("Speed:"); //Afterwards, show the fan speed percentage
                                                        display.print(fanSpeedPercent);
                                                        display.println("%");
                                                      }
                                                    } else {
                                                      display.println("Fan: OFF");
                                                    }
                                                  } else {
                                                    display.println("Sensor error");
                                                  }
                                                  
                                                  display.display();
                                                
                                                  delay(1000);
                                                }                                                                                                                               

                        
                    

Reflection and Struggles - Week 11

Connecting my Pi Pico and uploading the code was quite a challenge this week. I experimented with different libraries and, after thorough testing, discovered that the MicroDS18B20 library offers a more precise temperature measurement compared to the DallasTemperature library. This precision is crucial for accurately controlling the fan speed once I have my setup fully ready. Moreover, the Pi Pico was repeatedly stuck in the boot mode port UF2 Board, and while uploading the code, it indicated that the port was not connected, without giving me the option to change the port to COM7 or COM8, which is usually the port name when data was being transmitted. Therefore, I had to reset it using the BOOTSEL button multiple times, maneuver by first uploading a simple sketch, then my actual code to refresh the wireframes.

Assembling LapTable 1.0 Prototype - Week 12

I’m satisfied with the adjustable‐angle mechanism in the midsection. However, one major lesson I learned is never to cut pieces from the same assembly on two different CNC machines: they’ll have different tolerances and deviations, which makes fitting everything together much harder. As a result, I ended up using chisels and files to get all the pieces to fit properly. Although I originally designed everything to require no screws or glue, the dimensional variations—and the need for stability, especially in the legs—forced me to use a little glue and a few screws. For my final table, I definitely want to redesign the legs so they fold; that way I can store the table with maximum space efficiency. I also plan to allow more time for multiple iterations and to incorporate my instructor’s feedback several times before cutting the final design.
tabelprototypeangle
tableprototypeshot

Work Completed Up to the Midterm

I tested and experimented with the following aspects of my Lap Table:

  • PCB design and production: created a board to control the fan (output device) using temperature-sensor readings (input device).
  • Lap Table prototype 1.0: cut the first design and evaluated it.
  • Temperature-benchmark tests: determined the threshold for driving the fan in Arduino IDE.
  • DS18B20 sensor tests: verified the temperature sensor with a test fan.
  • PWM mapping: mapped temperature values to the fan-speed range.

Work Remaining After the Midterm

I still need to complete the following tasks for my Lap Table:

  • Redesign the Lap Table: improve stability and add new features.
  • Real-fan testing: map PWM values from the DS18B20 sensor to the speed of my 12 V DC fan.
  • Power-supply testing: evaluate a USB-PD (Power Delivery) trigger module with the electronics setup.
  • Cable management and electronics organizer: design and 3-D-print pieces to be integrated into the table, keeping ergonomics in mind.
  • Final evaluation: critically assess strengths and weaknesses to inform future projects.

Redesigning my Lap Table: Version 2.0 - Week 14

At this stage in my final project, I had to start redesigning my table. I started looking for inspiration on how I want my legs to be folding, bearing in mind the extra distance I need under the table surface for the electronics. I found a pretty cool long tutorial on building a small camping table with foldable legs to save storage space (check Axminster Tools - Folding Table). Hence, I decided to adapt the mechanism according to my needs. I started by designing extra wings along the length of the table on both sides and I offset both from the edge about the thickness of the wood stock (=15 mm). I removed the sides of the base rail and I kept only the middle part where the rod will be resting on the notches. Additionally, I offset the middle part of the base rail by another 15 mm to fit the extra wings, where the four legs will be pivoting. For the legs, I removed the press-fit joint I had prior (also removed all of the previous joints on the base left and the right sections of the table surface), and I added a fillet = 25 mm on both edges of the leg (replicated it for the rest). Lastly, I add cutout circles, d = 8 mm for all four legs and I aligned them with matching cutouts on the wings (d = 9 mm). To simulate the motion, I used the tool create components from bodies of the two wings (to be able to simulate the revolute joints), and I revolved each leg 90° around the pivot point to show the mechanism.
As for the bottom stopper of the middle section, I added some length of ~ 5 mm (for extra stability) and I added few notches to have a variety of angles in smaller steps to adjust the middle section with. As for the right and the left arms where the rod is the middle section is pivoting (adjustable angle), I adapted to the new parts by shifting the cutout circles in for the arms and wings by ~15 mm, and shortened them down accordingly.
TableBottomView
TableTopView
To stabilize the table better, I angled the legs 15°, and I added on each side a triangular stopper to help stop the legs from openning up fully. To keep the tension of the legs together, I added an 8 mm rod at the lower part of the legs and extended it from one side to the other!
legrods

Determining PWM range for my Actual Fan - Week 15

Finally, my fan arrived, so I wrote a simple code to determine the minimum and the maximum PWM range to map the speed fan accordingly (voltage input)


                                        /*
This code is to find out the PWM mapping range (min, max) for my fan
*/

const int FAN_PIN    = 16;    // PWM pin to fan MOSFET/gate

void setup() {
  Serial.begin(9600);
  pinMode(FAN_PIN, OUTPUT);
}
void loop() {
  for (int p = 0; p <= 255; p++) {
    analogWrite(FAN_PIN, p);
    Serial.print("PWM = "); Serial.println(p);
    delay(500);         // give the fan time to respond
  }
  while(1);            // stop after one loop
}

As you can see in the video, the fan starts to rotate with the PWM value hits 170. Hence, I changed it accordingly in my final project (draft) code - week 11!

Progress on System Integration - Week 15

System Integration Overview

overview

Final Designs of All Electronics Housings

PCBslidebox
Housing for the PCB
OLEDDisplayHousing
Housing for the OLED03.1 Display
DS18B20housing
Housing for the DS18B20 Tempertaure Sensor
PDModulHousingupdated
Housing for the USB-C PD Module
powerbankholder
Holder for the Powerbank

Assembly for Each Electronic Component + its Housing

housingPCB
Housing + PCB
housingdisplay
Housing + OLED03.1 Display
housingtempsensor
Housing + DS18B20 Tempertaure Sensor
housingPDmodule
Housing + USB-C PD Module
housingpowerbank
Holder + Powerbank
For further details on the design and the assembly process, please check System Integration - Week 15

Engraving a Pattern and Drilling my Lap Table

In order to have hide nicely the cutouts for the sensors, I found this tree pattern to engrave in the middle section of my table. First, I downloaded the .png file and I used Picsvg converter to convert it to an .svg file. Then, I went to inkscape to apply trace bitmap in order to see the contrast (black and white) where the pattern will be engraved on the table.
Tracebitmaptree
exportpdftreepattern
Once I scaled it to A4 paper, I exported it as a pdf and printed it on an A4 paper. Then, I placed it physically on the table and measured exactly the dimensions where I want it to be. The dimensions where ~ 265 mm × 170 mm. Then, I measured where the laser head has to exactly jog it there manually and start the engraving job.
measuringtreepattern
Afterwards, I went to Rhino 7 to manipulate the pdf file and exactly fit it to the dimensions I previously measured.
scalingtreepatternrhino7
Now, with the shortcut ctrl + p, I went to the Epilog Dashboard software, and I set the engraving parameters and I set the power to 60% and speed to 95%.
Lasercutparameterstreepattern
Lastly, I sent the engraving job to our Fpilog Laser Fusion 60 Watts CO2 Laser, after I made sure that the laser head is exactly at the upper left corner point of the pattern to be engraved. The whole process took 17 min to finish.

Progress on System Assembly - Week 16

Due to the powerbank housing being too big to mount on the bottom surface of the table on the left corner, I decided to redesign the mounting ears; decrease their size and rotate them 90° to hold the powerbank on the vertical access rather than the horizontal. Moreover, the cutout for the USB slots was unified and enlarged for easier access for all four slots. Hence, with the help of my instructor, I managed to do that. Printing using the Bambu Lab A1 took roughly ~ 3 h!
powerbankhousingupdateddesign
Updated Fusion360 Design for Powerbank Housing
slicingnewpowerbankhousing
Slicing and Printing the Updated Powerbank Housing in Bambu Lab A1
powerbanhousingupdatedfit
Updated Fit for Powerbank Housing
Testing the fiting, it was clear for me that now I can mount it right next to the PCB and the PD module much more comfortably. The only thing left now is the cable management!
powerbankhousingbeforeadjustingears
Powerbank Housing Fit Before Modifications
powerbankhousingafteradjustingears
Powerbank Housing Fit After Modifications

Progress on Cable Management - Week 17

measuringlengthtoadd
Measuring the extra length needed for hinge travel
After testing the full range of motion, I found that the DS18B20 sensor cables needed an extra ~ 45 cm and the fan requires an extra ~ 25 cm and the display requires an extra ~ 20 cm to avoid any pulling when the table top is angled. Hence, I used both 0.05 mm² (red and yellow; Vcc and Dig) and 0.14 mm² (black; GND) diameters wires, cut and soldered them appropriately to the original jumper cables, then sealed each link with heat-shrink tubing for durability and safety.
elongatesensorscables
Extending DS18B20 sensor Cabling by 45 cm (with heat-shrink protection)
elongatefanscables
Extending Fan Cabling by 25 cm
elongatingdisplaycables
Extending Display Cabling by 20 cm
Further more, using the crimping tool, I crimped connectors to secure the fan cables (Vcc, GND) in the terminal connector.
crampingcable
crimpingconnectorsinserted

Printing Zip Tie Cable Holder

In order to route the cables neatly, I downloaded the .stl file from Printables - Zip Tie Cable Holder. I cloned 10 of them in the bambulab studio. I also recorded the embedded feature of timelapse in the Bambu Lab A1 printer. The print for all these cable holders took ~ 45 min.
ziptiescableholderdoubletape
Zip Ties + 3D Printed Cable Holders + Double Tape
In order to have a clean look for all of the cables, I wrapped a spiral sleeve around the display cables as well as the cables from both sensors and fan to merge them into one path before reaching the PCB box for smoother neater integration.
routingfancables
addingspiralsleeve

Creating UI Using Processing - Week 18

In order to experiment with creating more UI, I decided to recycle the processing code I made with ChatGPT in Week 14 - Interface and Application programming. I decided to try and code again with ChatGPT to see how well the processing code it creates works!

Prompt

    I attached both codes ("FinalFACodeDraft.ino"and "GUIStripChart.pde") and wrote:
  • "How do I integrate my Arduino sketch "FinalFACodeDraft.ino" into my Processing sketch "GUIStripChart.pde", so I can print on my screen using processing the fan speed and the mean temperature showing on the gauge? please createfor me a processing code for this reason!"

Processing Code

This code establishes a serial connection to the Pi Pico at 9600 baud rate, where it receives four semicolon-separate values: mean temperature, readings from two DS18B20 temperature sensors, and the fan speed percentage. fter obtaining these values, the drawing interprets the readings and creates a meter gauge to show the mean temperature. Furthermore, it displays the fan speed percentage as text underneath the gauge.

                                      /*
 -  Pi Pico 2 + two temperature sensors DS18B20 + fan 
 -  Libraries used: Serial (built-in)  |  ControlP5 | Meter
 */
 
// Import Libraries
import processing.serial.*;   // built-in serial comms
import controlP5.*;           // ControlP5 GUI widgets
import meter.*;               // external Meter library

// Declare global objects
Serial    pico;               // declare Serial object 'pico' for Arduino connection
ControlP5 cp5;                // declare ControlP5 object 'cp5' for GUI controls
Meter     tempGauge;          // declare Meter object 'tempGauge' to display mean temperature

// define constants for minimum and maximum temperature values used in gauges and plots
final float MIN_T = 20;       // minimum temperature to display on gauge and plot
final float MAX_T = 50;       // maximum temperature to display on gauge and plot

// arrays to store historical temperature readings for two sensors (for strip chart)
float[] hist1 = new float[60]; // history buffer for sensor 1 readings (60 samples)
float[] hist2 = new float[60]; // history buffer for sensor 2 readings (60 samples)
int dataIndex = 0;             // index into history buffers, wraps around after reaching end

// variables to hold the latest readings received from Arduino
float currentMean = 0;         // latest computed mean temperature
float currentT1   = 0;         // latest sensor 1 temperature
float currentT2   = 0;         // latest sensor 2 temperature
int   currentFanSpeed = 0;     // latest fan speed percentage

boolean stream = true;         // flag controlling whether to update and draw live data

void setup() {
  size(900, 600);             // set window size to 900x600 pixels
  surface.setTitle("Temp & Fan Monitor"); // set the window title

  // Serial Setup   
  println("Available serial ports:");      // print list header
  println(Serial.list());                  // list all available serial ports in console
  pico = new Serial(this, Serial.list()[0], 9600); // open first available port at 9600 baud
  pico.bufferUntil('\n');                 // buffer incoming serial data until newline arrives

  // ControlP5 Toggle
  cp5 = new ControlP5(this);               // initialize ControlP5 with this sketch
  cp5.addToggle("stream")                 // add a toggle button named 'stream'
     .setPosition(20, 20)                  // position toggle at x=20, y=20
     .setSize(50, 20)                      // set toggle width=50, height=20
     .setValue(true)                       // default value is true (streaming on)
     .setMode(ControlP5.SWITCH);           // set toggle mode to a switch

  // Meter Gauge Setup
  tempGauge = new Meter(this, width/2 - 200, 20); // create Meter centered horizontally, y=20
  tempGauge.setTitleFontName("Arial Bold");      // set the font for the gauge title
  tempGauge.setTitleFontSize(20);                 // set the gauge title font size
  tempGauge.setTitle("Mean Temp (°C)");          // set gauge title text
  tempGauge.setMinInputSignal((int)MIN_T);         // set minimum input signal value on gauge
  tempGauge.setMaxInputSignal((int)MAX_T);         // set maximum input signal value on gauge
  tempGauge.setMinScaleValue((int)MIN_T);          // set minimum scale tick label
  tempGauge.setMaxScaleValue((int)MAX_T);          // set maximum scale tick label

  // Build and set scale labels every 2°C between MIN_T and MAX_T
  String[] labels = new String[(int)((MAX_T - MIN_T)/2) + 1]; // array for labels
  for (int i = 0; i < labels.length; i++) {       // loop through each label index
    labels[i] = nf(MIN_T + i*2, 0, 0);             // format number with no decimals
  }
  tempGauge.setScaleLabels(labels);                // apply the labels to the gauge

  tempGauge.setArcColor(color(60));                // set the arc color of the gauge
  tempGauge.setArcThickness(15);                   // set thickness of gauge arc
  tempGauge.setNeedleThickness(4);                 // set thickness of gauge needle
  tempGauge.setDisplayDigitalMeterValue(true);     // enable digital readout on gauge
}

void serialEvent(Serial s) {
  String line = trim(s.readStringUntil('\n')); // read newline-terminated data, then trim whitespace
  if (line == null || line.length() == 0) return; // ignore empty or null lines

  // expect a line formatted as "mean;t1;t2;fanSpeed"
  String[] parts = split(line, ';');          // split the line into parts by semicolon
  if (parts.length == 4) {                    // only proceed if exactly four parts received
    currentMean     = parseFloat(parts[0]);  // parse part 0 as float for mean temperature
    currentT1       = parseFloat(parts[1]);  // parse part 1 as sensor 1 temperature
    currentT2       = parseFloat(parts[2]);  // parse part 2 as sensor 2 temperature
    currentFanSpeed = int(parseFloat(parts[3])); // parse part 3 as float then convert to int for fan speed
    
    // push new readings into history buffers at current dataIndex
    hist1[dataIndex] = currentT1;            // store t1 reading
    hist2[dataIndex] = currentT2;            // store t2 reading
    dataIndex = (dataIndex + 1) % hist1.length; // increment index with wrap-around
  } // end if correct parts
  // else: ignore malformed lines
}

void draw() {
  background(0);                              // clear the screen with black background

  if (stream) {                               // if streaming is enabled
    tempGauge.updateMeter(int(currentMean));  // update gauge needle to current mean temperature

    // display fan speed text underneath the gauge
    fill(255);                                // set fill color to white
    textAlign(CENTER, CENTER);                // center-align text both horizontally and vertically
    textSize(18);                             // set text size to 18
    text("Fan Speed: " + currentFanSpeed + "%",
         width/2 + 40,                      // x-position middle of the gauge
         height - 290);                         // y-position below the gauge
  } else {                                    // if streaming is disabled
    fill(255);                                // white text
    textAlign(CENTER, CENTER);                // centered text
    textSize(24);                             // larger text size for message
    text("Stream OFF", width/2, height/2 - 50); // display "Stream OFF" message
  }

  // always draw the strip-chart of sensor histories
  drawPlot();                                 // call helper to draw the time-series plot
}

void drawPlot() {
  int plotTop    = 300;                       // y-coordinate for top of plot area
  int plotBottom = height - 40;               // y-coordinate for bottom of plot area
  int marginLeft = 60;                        // x-coordinate for left margin of plot
  float spacing  = float(width - marginLeft - 20) / (hist1.length - 1); // horizontal spacing between points

  // draw sensor 1 history in cyan
  stroke(0,255,255);                          // set stroke color cyan
  strokeWeight(2);                            // line thickness 2
  noFill();                                   // no fill for shape
  beginShape();                               // start shape for polyline
  for (int i = 0; i < hist1.length; i++) {    // iterate through history buffer
    int p = (dataIndex + i) % hist1.length;   // calculate wrapped index for circular buffer
    float x = marginLeft + i * spacing;      // compute x position
    float y = map(hist1[p], MIN_T, MAX_T, plotBottom, plotTop); // map temperature to y-coordinate
    vertex(x, y);                             // add vertex to shape
  }
  endShape();                                 // finish drawing polyline

  // draw sensor 2 history in salmon (offset by +0.5°C for clarity)
  stroke(255,102,102);                        // set stroke color salmon
  strokeWeight(2);                            // line thickness 2
  noFill();                                   // no fill
  beginShape();                               // start second polyline
  for (int i = 0; i < hist2.length; i++) {    // iterate through second history buffer
    int p = (dataIndex + i) % hist2.length;   // wrapped index
    float x = marginLeft + i * spacing;      // compute x-coordinate
    float y = map(hist2[p] + 0.5, MIN_T, MAX_T, plotBottom, plotTop); // map t2+0.5°C
    vertex(x, y);                             // add vertex
  }
  endShape();                                 // end second polyline

  // draw axes and labels
  stroke(200);                                // set stroke color light gray for axes
  strokeWeight(1);                            // thin line for axes
  line(marginLeft, plotTop, marginLeft, plotBottom); // draw y-axis
  noStroke();                                 // disable stroke for text
  fill(200);                                  // set fill color for text labels
  textAlign(RIGHT, CENTER);                   // align text right
  text(MIN_T + "°", marginLeft-5, plotBottom); // label bottom of y-axis
  text(MAX_T + "°", marginLeft-5, plotTop);    // label top of y-axis
  textAlign(LEFT, CENTER);                    // align sensor legend text left
  fill(0,255,255);                            // cyan for sensor 1 label
  text("Sensor 1", marginLeft+5, plotTop+10); // position label
  fill(255,102,102);                          // salmon for sensor 2 label
  text("Sensor 2", marginLeft+5, plotTop+30); // position label
}

// callback invoked by ControlP5 when the 'stream' toggle changes state
public void stream(boolean active) {
  stream = active;                            // update the streaming flag
}

                                           

Final Arduino IDE Code Version

As for the final sketch in Arduino IDE, I had to just tweak the Serial.print for all four variables: meanTemp, Temp1, Temp2, and fanSpeedPercent in order for them to be recognized as four variables in Processing. Therefore, I added a
Serial.print(';');
after every value.


                               /*
 Leen Skaf 05/29/2025
//
This work may be reproduced, modified, distributed,
performed, and displayed for any purpose, but must
acknowledge this project. Copyright is retained and
must be preserved. The work is provided as is; no
warranty is provided, and users accept all liability.
// 
//
The following code is a draft for my final project
- Drive a fan based on the mean temperature recorded by two DS18B20 temperature sensors 
- To control the speed of the fan, it is mapped to the PWM range of 170 to 255, where a minimum temperature threshold is set and an increment of 20% speed increase corresponds to 1°C temperature increased
- Information about fan status and mean temperature is shown on an OLED01.3 display 
//
*/
#include 
#include 
#include 
#include 

// I2C OLED Setup 
#define I2C_ADDRESS 0x3C  
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Initialize the OLED display using the default I2C bus (Wire)
Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

// DS18B20 Temperature Sensors 
// Sensors are connected to GPIO26 and GPIO27
MicroDS18B20<26> sensor1;
MicroDS18B20<27> sensor2;

// LED and Fan Definitions 
#define LED_PIN 22   // LED to indicate temperature threshold
#define FAN_PIN 16   // DC fan controlled via PWM

// Temperature thresholds for fan control:
const float tempThreshold = 22.0;  // Fan starts running at 22°C
const float maxTempForFan = 26.0;    // Fan reaches 100% speed at 27°C

// Global variable to track when the fan turns on (in m illiseconds)
unsigned long fanOnStartTime = 0;

void setup() {
  //delay(2000);
  Serial.begin(9600);

  // Initialize I2C bus
  Wire.begin();

  // Initialize the OLED display; if not found, pause execution
  if (!display.begin(I2C_ADDRESS, true)) {  
    Serial.println(F("OLED not found"));
    while (1);
  }

  // Configure the LED and Fan pins as outputs
  pinMode(LED_PIN, OUTPUT);
  pinMode(FAN_PIN, OUTPUT);
  // Display a startup message
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(0, 10);
  display.println("Starting...");
  display.display();
  //delay(2000);
}

void loop() {
  // Request temperature readings from both sensors
  sensor1.requestTemp();
  sensor2.requestTemp();
  
  // Wait for conversion.
  delay(1000);
  
  // Variables to hold temperature values and sensor status
  float temp1 = 0, temp2 = 0;
  bool sensor1_ok = false, sensor2_ok = false;
  
  // Read sensor1.
  if (sensor1.readTemp()) {
    temp1 = sensor1.getTemp();
    sensor1_ok = true;
  } else {
    Serial.println("Error reading sensor1");
  }
  
  // Read sensor2.
  if (sensor2.readTemp()) {
    temp2 = sensor2.getTemp();
    sensor2_ok = true;
  } else {
    Serial.println("Error reading sensor2");
  }
  
  // Validate sensor data.
  bool validData = sensor1_ok && sensor2_ok;
  float meanTemp = 0;
  
  if (validData) {
    meanTemp = (temp1 + temp2) / 2.0;

  } else {
    Serial.println("Cannot compute mean: one or more sensor readings failed.");
  }
  
  // Variables for fan control.
  bool fanOn = false;
  int fanSpeedPercent = 0;
  int pwmValue = 0;  // PWM value (0 - 255)

  // Only control the fan if sensor data is valid
  if (validData && (meanTemp >= tempThreshold)) {
    digitalWrite(LED_PIN, HIGH);  // Turn on LED

    // Calculate fan speed percentage based on temperature between threshold and maxTempForFan
    if (meanTemp >= maxTempForFan) {
      fanSpeedPercent = 100;
    } else {
      fanSpeedPercent = (int)(((meanTemp - tempThreshold) / (maxTempForFan - tempThreshold)) * 100);
    }

    
    // Map the fan speed percentage to PWM value in the range 170 to 255
    pwmValue = map(fanSpeedPercent, 0, 100, 170, 255);
    analogWrite(FAN_PIN, pwmValue);
    fanOn = true;
    
    // Record fan activation time if it just started
    if (fanOnStartTime == 0) {
      fanOnStartTime = millis();
    }
  } else {
    digitalWrite(LED_PIN, LOW);   // Turn off LED
    analogWrite(FAN_PIN, 0);        // Turn off fan
    fanSpeedPercent = 0;
    fanOnStartTime = 0;
  }

  // Send mean; temp1; temp2; fanSpeed%  to Processing:
  Serial.print(meanTemp);
  Serial.print(';');
  Serial.print(temp1);
  Serial.print(';');
  Serial.print(temp2);
  Serial.print(';');
  Serial.println(fanSpeedPercent);
  
  // Update the OLED display.
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(0, 0);
  
  if (validData) {
    // First line: Mean temperature with unit Celcius degrees
    display.print("Mean:");
    display.print(meanTemp, 1);
    display.print("C");
  
    // Second line: Fan status/speed
    display.setTextSize(2);
    display.setCursor(0, 35);
    if (fanOn) {
      // For the first 2 seconds, display "Fan: ON"
      if (millis() - fanOnStartTime < 2000) {
        display.println("Fan: ON");
      } else {
        display.print("Speed:"); //Afterwards show the fan speed percentage
        display.print(fanSpeedPercent);
        display.println("%");
      }
    } else {
      display.println("Fan: OFF");
    }
  } else {
    display.println("Sensor error");
  }
  
  display.display();
  delay(1000);
}



// References:

//
// Adafruit Industries. (2025). Adafruit GFX graphics library. GitHub.
// Retrieved March 27, 2025, from https://github.com/adafruit/Adafruit-GFX-Library
//
// Adafruit Industries. (2025). Adafruit SH110X library. GitHub.
// Retrieved March 27, 2025, from https://github.com/adafruit/Adafruit_SH110X
//
// Arduino. (n.d.). microDS18B20 library. Arduino Library documentation. 
// Retrieved April 8, 2025, from https://docs.arduino.cc/libraries/microds18b20 
//
// Arduino. (2025). analogWrite() function. Arduino Documentation. 
// Retrieved March 29, 2025, from https://docs.arduino.cc/language-reference/en/functions/analog-io/analogWrite
//
// Wright, J. (2021). microDS18B20 library. GitHub.
// Retrieved March 27, 2025, from https://github.com/jwright235/microDS18B20
//
                                                            

Fixating Powerbank, PCB, and PD Module Housings

As for the PD Module and the PCB box, I decided to fixate them with double-sided tape, since I still plan to reiterate designing the whole table after Fab Academy. Nevertheless, this was not possible with the powerbank housing, due to its heavy weight. Therefore, I had to go with the screws option for fixating it onto the table bottom surface. For this I positioned it vertically right below the PCB, drilled and fixated it using four 3 mm × 15mm screws.

Final Bottom View of my Lap Table

finaltablebottomview

Final 360° View of my Lap Table

Final Reflection

Finally, we reached the end of this, rich, full of happy and stressed tears journey. I could not be more grateful; I learned about so many different topics and indulged in so many fields, such as 2D and 3D designs, electronics design and production, and Interface design. The last step left to complete is to create my video clip of developing my whole project, as well as the slide summary describing all the designing, manufacturing, and polishing stages involved. Further, in order to continue the footprint of the Fab Academy community, I will implement few strategies to disseminate my project and help future students :D
P.S. I would like to thank my teammates for sharing this experience with me: Mika and Lysander. They made our group assignments much more tolerable and enjoyable, and I learned a lot from them! Special thanks to my instructor Ahmed for patiently supporting us throughout all the weeks.

Materials

19.95 1000 x 63.1
Qty Description Price Link
one Raspberry Pi Pico W 6.99€ Raspberry Pi Pico W
one MOSFET SOT-23 0.37€ MOSFET
two DS18B20 Temperature Sensor Module 2.40 € DS18B20 shield
one Diode SOD123 0.25€ SOD123 Diode
one SBC-OLED01.3 10.50€ SBC-OLED01.3
one 60x60x15mm / 12V / 0,026A Fan 2.60€ DC 12V Fan
one USB PD (Power Delivery) trigger module 11.20€ USB PD (Power Delivery) trigger module
one Verbatim PLA Filament 1.75mm 1 kg - Blue ~ 1.36 € (68.1 g × 19.95 €/ 1000 g)
one Verbatim PLA Filament 1.75mm 1 kg - Black ~ 1.36 € (68.1 g × 19.95 €/ 1000 g)
one Verbatim PLA Filament 1.75mm 1 kg - White ~ ~ 1.36 € (68.1 g × 19.95 €/ 1000 g)
one 900 mm × 600 mm × 24 mm (0.54 m²) Plywood ~ 60 € (Rail base) Note: This was the last piece at the store Hagebau Kleve, hence, there is no version of it online
one 600 mm × 1200 mm × 18 mm (0.72 m²) Plywood ~ 8.33 € (legs → 0.015 m² × 138.88 €/m² × 4)
one 600 mm × 1200 mm × 12 mm (0.72 m²) Plywood ~ 25.87 € (table surface, stoppers and arms → 0.35152 m² × 73.60 €/m²)
Total 134.99 €

YouTube Tutorials:

  • Testing NEW CNC Joint - Bed-Table Build
  • DoveTail Half-Lap Joint
  • Basic Wood Joinery- Eric Brennan
  • Axminster Tools, 2023, Make a Folding Camping Table - Woodworking Wisdom
  • KiCAD 7 PCB Layout in 5 steps
  • KiCad 7 ESP32 PCB Design Full Tutorial - made by morten laboratories iot-thing
  • Master New KiCad 7 In Under 2 Hours | #PCBCUPID

Files