Skip to content

Final Project

Moi is a small air humidifier device that can be placed on a table in any interior. It is improving air quality in the area and also it’s serving as a lamp when LEDs are on.

alt text

Project tracking

What have been done / what haven’t been

  • Concept development


    • Research
    • Ideation
    • Concept testing

    Developed an idea of product, it’s purpose and function, considered possible electronics component which will be used, made a test board and tested components, figured out voltage consumption and control possibilities.

  • Design


    • Industrial sketching
    • Draft modeling in VR
    • CAD modeling

    I used 2D and 3D modeling capabilities for my project design. Used 2D to come up with ideas for product appearance as well to measure all the parts and components I use and to create full assembly in CAD program.

  • Fabrication


    • 3D printing
    • Laser cutting

    I used additive and subtractive fabrication techniques for making different parst and assemby them to one integrated system with separated zones for water, air and electronics component.

  • Electronics production


    • PCB design
    • PCB milling
    • Soldering
    • System integration

    Used PCB production tools in conjunction with CAD assembly to create a PCB with desired shape and dimentions. Made an according composition of components on the board to fit in the design. Made heating components upside down to pretend overheating in the narrow area and give more free space for cooling. Made an electronics box to cover all the electronics part and electronic devices in one integrated closed system.

  • Embedded programming


    • Microcontroller programming
    • Mobile application development
    • Wireless control interface development

    Wrote a program for microcontroller to control devices switching time. It’s still remaining to make a wireless control interface like mobile application.

  • Assembly


    • Water isolation
    • PCB isolation with epoxy
    • Finishing
    • Assembling

    Used silicone sealant to isolate water tank. Cleaned 3D print supports, connected electronics and devices, assembled. It’s still remaining to make an epoxy cover on PCB to make sure it’s protected from water.


Project development

  • Tasks completed / not completed

    • Water flow system
    • PCBs for electronics devices control
    • Power regulation

    • Wireless control application
    • Water refill hole
    • PCB isolation with epoxy
  • What worked / what didn’t


    • Mist maker switching control
    • Pump works as the water runs out
    • Addressable LEDs work programmed

    • Voltage regulator overheats
    • Mist maker pits and mist doesn’t reach high
    • Piezoelectric mist maker disk
  • What questions need to be resolved


    • Mobile application development
    • Control over WiFi (add WiFi module)
    • How to make the pump silent
    • Replace mist maker device with piezoelectric mist maker disk
    • How to make water refill hole and cap. What materials to use

Planning

  • Week 2


    Sketched ideas, 3D modeled draft design.

    Computer Aided design

  • Week 9


    Tested out mist maker device, controlled with transistors.

    Output devices

  • Week 11


    Made water level sensor with copper sheets and tested.

    Input devices

  • Week 13


    Tested communication over WiFi. Between ESP12-F with wifi modules.

    Networking and Communications

  • Week 14


    Made a test interface in Blynk for mobile application and tested control ESP12-F over Wifi.

    Interface and Application

  • Week 16-20 - This page


    Finished 3D model, started fabrication processes, made a final PCB, Programmed.


Process

alt text

Final project started with sketching ideas, where I approximately considered what it will be built from and what parts it will contain.

alt text

Draft 3D models were done in VR.

alt text

3D modeling and fabricating parts

alt text

I used Fusion 360 for making 3D model of my final project.

It consists of three 3D printed parts

  • Water tank

Stores water and integrates a mist maker for creating a cool mist. A designated filling port on the bottom allows for refilling water. It’s connected to acrylic sheet and isolated from bottom parts. 3D Printed Construction: The tank is 3D printed with translucent material, allowing for a unique aesthetic and for internal light features. 3D printed with variable layer heights between 0.2mm and 0.8mm. This optimizes material usage and potentially enhances strength or visual effects. The translucent material reflects light from internal sources, creating an attractive illuminated effect. The center of the tank incorporates a dedicated area for connecting a mist maker device. This includes: Pipe connection point for water delivery to the mist maker. Also through central part is dedicated space for sensor wires and mist maker’s connection wires.

  • Electronics box

Includes in it PCB, big components are fitted in this part: mist maker device, water pump. Special space is dedicated for THT transistors, voltage regulator, connection jack and connectors and microcontroller. Part is 3D printed, fits in bottom case, has parts for screwing to the acrylic sheet above it.

  • Bottom case

3D printed with special texture, reached with slicing setting fuzzy skin, connects to the water tank with thread, Provides a connection to the power jack from outside.

s

Water tank and bottom case have a joining with thread. It’s 90 degree rotation for full close. For this parts I tested thread with different offsets and final version was 0.8 mm total.

Water tank and bottom case

Electronics box part.

alt text

Lasercutting

Laser cut acrylic sheet which fits into bottom part of water tank and connects with silicone sealant. For better adhesion of sealant here is done engravings along the edges.

alt text

Post-processing

Prepared 3D printing with silicone for preventing leaking. because wall thickness was 2 layers 0.8mm = 1.6mm

alt text

Mist maker fixed on water tank.

alt text

Here connects the acrylic sheet.

alt text

Isolated with silicone sealant

PCB production

I have already tested mist maker, pump and voltage regulation on test board. This time I decided to use RP2040. amount of pins on this board was enough to control all devices and sensors. Here is a schematic.

alt text

alt text

Microcontroller:

  • Seeed XIAO RP2040

Inputs:

  • Capacitive touch
  • Resistive touch x2

Outputs:

  • Fogger
  • PUMP
  • addressable LEDs

Communication:

  • Bluetooth module

Power:

  • 24V input
  • Voltage regulator 24V to 5V

Designed the board with using all pins of the microcontroller. Some of connections are used, some are designed for any case like capacitive touch connection, bluetooth connection and clock/data connections.

Designed PCB according the shape of electronics box. It’s circle shaped, there is a space for mist maker to fit in the middle gap part. PCB is from 2 pieces. The reason of it was that if some of parts will not work, to change not all PCB but one part of it, and also the biggest size of our copper plate was 7x10 cm, so I used two of these.

alt text

Two parts will be connected to each other with connectors. Here is one of them.

alt text

And connected both parts.

alt text

This PCB contains addressable LEDs, which are soldered right on the board. Thats why right above the PCB is transparent acrylic sheet. And on top side of PCB there would be no space to solder big components. That’s why I designed this board to be some kind of two sided and THT components and microcontroller are soldered upside down.

alt text

This not only saves space above the PCB but also prevents these component from being overheated.

Programming

The microcontroller used in the project is Seeed Xiao RP2040.

alt text

Outputs

  • Addressable LED 5 V

alt text

For lighting is used addressable led strip. LEDs are separated from the strip and arrayed on the PCB on a particular distance. For using LEDs separated from the LED strip Areg created footprint for KiCad with according sizes. Download link.

img

This LEDs in my project are programmed by using Adafruit NeoPixel library. Download link.

  • Ultrasonic mist maker 24 V

alt text

With some tests I find out how this device works. It has water level sensor on it as well that’s why I needed to make this part to be sealed in the water. And what shows this test the sensor and the piezo disc parts need to have conduction through water.

I wanted to make the mist coming from piezo element part to rise into the air. that’s why I designed the water tank to have empty space in the middle. The challenge was keeping the water level in the container full and preventing water from getting in the middle part through the fogger’s holes. For keeping low water level in that middle part I used pump and pipes.

  • Water pump 3-5 V.

alt text

This pump can be sealed in the water. But for aesthetic purposes I din’t wat it to be visible in the water tank through transparent material.

I also used a pump to pump a small amount of water into the fog machine. I did this because I couldn’t submerge the entire fogger in water and had to separate the main water container and the fogger. And with the help of a pump, I connected these two spaces to control the amount of water entering the fogger.

Input - resistive sensor

I also added a resistive sensor to automate this process. Sensor detects when water is low. It’s two wires, one placed in the maximum water level needed, the other in the lowest part.

In simple words, when there was no water above fogger, my device recognized this and supplied water, and when the water rose to the maximum level and contacted the resistive sensor, the supply stopped until it completely evaporated.

            #include <Adafruit_NeoPixel.h> // Adafruit library
            #ifdef __AVR__ 
            #include <avr/power.h>  // Required for 16 MHz Adafruit Trinket
            #endif

            #define PIXEL_PIN 29  // Pin to control NeoPixels.
            #define PIXEL_COUNT 6  // Number of NeoPixels

            #define pumpPin 2 // Pin to control water pump
            #define foggerPin 4 // Pin to control Mist maker device
            #define sensorPin 26 // Pin wired to insput 

            // Declare our NeoPixel strip object:
            Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
            // Argument 1 = Number of pixels in NeoPixel strip
            // Argument 2 = Arduino pin number (most are valid)
            // Argument 3 = Pixel type flags, add together as needed:
            //   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
            //   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
            //   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
            //   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
            //   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)

            boolean oldState = HIGH;
            int mode = 0;  // Currently-active animation mode, 0-9

            unsigned long currentMillis = 0;


            int pumpOnTime = 2000;
            bool isPumpOn = false;
            int sensorThreshold = 400;

            void setup() {
            strip.begin();  // Initialize NeoPixel strip object (REQUIRED)
            strip.show();   // Initialize all pixels to 'off'
            rainbow(10);

            pinMode(sensorPin, INPUT); // Defined as input
            pinMode(pumpPin, OUTPUT); // defined as output
            currentMillis = millis();

            pinMode(foggerPin, OUTPUT);
            digitalWrite(foggerPin, HIGH); // Fogger state ON
            }

            void loop() {
            int sensorVal = analogRead(sensorPin);  // read values from sensor
            //Serial.println(sensorVal);
            if (sensorVal < sensorThreshold && !isPumpOn) { // when sensor value is low then threshold turn on pump
                isPumpOn = true;
                Serial.println("pump off to on");
                digitalWrite(pumpPin, HIGH);
                currentMillis = millis();
            }
            // Get current button state.
            if (isPumpOn) {
                if (millis() - currentMillis > pumpOnTime) { // measure time the pump is on and turn off after 2 seconds.
                isPumpOn = false;
                Serial.println("pump on to off");
                digitalWrite(pumpPin, LOW);  // Pump OFF
                }
            }


            if (++mode > 8) mode = 0;  // Advance to next mode, wrap around after #8
            switch (mode) {            // Start the new animation...
                case 0:
                colorWipe(strip.Color(0, 0, 0), 50);  // Black/off
                break;
                case 1:
                colorWipe(strip.Color(255, 0, 0), 50);  // Red
                break;
                case 2:
                colorWipe(strip.Color(0, 255, 0), 50);  // Green
                break;
                case 3:
                colorWipe(strip.Color(0, 0, 255), 50);  // Blue
                break;
                case 4:
                theaterChase(strip.Color(127, 127, 127), 50);  // White
                break;
                case 5:
                theaterChase(strip.Color(127, 0, 0), 50);  // Red
                break;
                case 6:
                theaterChase(strip.Color(0, 0, 127), 50);  // Blue
                break;
                case 7:
                rainbow(10);
                break;
                case 8:
                theaterChaseRainbow(50);
                break;
            }

            delay(2000);
            }

            // Fill strip pixels one after another with a color. Strip is NOT cleared
            // first; anything there will be covered pixel by pixel. Pass in color
            // (as a single 'packed' 32-bit value, which you can get by calling
            // strip.Color(red, green, blue) as shown in the loop() function above),
            // and a delay time (in milliseconds) between pixels.
            void colorWipe(uint32_t color, int wait) {
            for (int i = 0; i < strip.numPixels(); i++) {  // For each pixel in strip...
                strip.setPixelColor(i, color);               //  Set pixel's color (in RAM)
                strip.show();                                //  Update strip to match
                delay(wait);                                 //  Pause for a moment
            }
            }

            // Theater-marquee-style chasing lights. Pass in a color (32-bit value,
            // a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
            // between frames.
            void theaterChase(uint32_t color, int wait) {
            for (int a = 0; a < 10; a++) {   // Repeat 10 times...
                for (int b = 0; b < 3; b++) {  //  'b' counts from 0 to 2...
                strip.clear();               //   Set all pixels in RAM to 0 (off)
                // 'c' counts up from 'b' to end of strip in steps of 3...
                for (int c = b; c < strip.numPixels(); c += 3) {
                    strip.setPixelColor(c, color);  // Set pixel 'c' to value 'color'
                }
                strip.show();  // Update strip with new contents
                delay(wait);   // Pause for a moment
                }
            }
            }


            void rainbow(int wait) {   // Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
            // Hue of first pixel runs 3 complete loops through the color wheel.
            // Color wheel has a range of 65536 but it's OK if we roll over, so
            // just count from 0 to 3*65536. Adding 256 to firstPixelHue each time
            // means we'll make 3*65536/256 = 768 passes through this outer loop:
            for (long firstPixelHue = 0; firstPixelHue < 3 * 65536; firstPixelHue += 256) {
                for (int i = 0; i < strip.numPixels(); i++) {  // For each pixel in strip...
                // Offset pixel hue by an amount to make one full revolution of the
                // color wheel (range of 65536) along the length of the strip
                // (strip.numPixels() steps):
                int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
                // strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
                // optionally add saturation and value (brightness) (each 0 to 255).
                // Here we're using just the single-argument hue variant. The result
                // is passed through strip.gamma32() to provide 'truer' colors
                // before assigning to each pixel:
                strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
                }
                strip.show();  // Update strip with new contents
                delay(wait);   // Pause for a moment
            }
            }

            // Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
            void theaterChaseRainbow(int wait) {
            int firstPixelHue = 0;           // First pixel starts at red (hue 0)
            for (int a = 0; a < 30; a++) {   // Repeat 30 times...
                for (int b = 0; b < 3; b++) {  //  'b' counts from 0 to 2...
                strip.clear();               //   Set all pixels in RAM to 0 (off)
                // 'c' counts up from 'b' to end of strip in increments of 3...
                for (int c = b; c < strip.numPixels(); c += 3) {
                    // hue of pixel 'c' is offset by an amount to make one full
                    // revolution of the color wheel (range 65536) along the length
                    // of the strip (strip.numPixels() steps):
                    int hue = firstPixelHue + c * 65536L / strip.numPixels();
                    uint32_t color = strip.gamma32(strip.ColorHSV(hue));  // hue -> RGB
                    strip.setPixelColor(c, color);                        // Set pixel 'c' to value 'color'
                }
                strip.show();                 // Update strip with new contents
                delay(wait);                  // Pause for a moment
                firstPixelHue += 65536 / 90;  // One cycle of color wheel over 90 frames
                }
            }
            }

I also made a second version of the code where I could control the feed without using a sensor. To do this, I measured the time for complete evaporation of one portion of water and thus made a delay timer for water supply and evaporation. It took 1 second to serve and 9 seconds to evaporate.

void loop() {
  digitalWrite(pumpPin, HIGH);
  digitalWrite(foggerPin,HIGH);
  delay(1000);
  digitalWrite(foggerPin,LOW);
  digitalWrite(pumpPin, LOW);
  delay(9000);
}

BOM

Qty Description Price Link Notes
1 Fog maker 11.00 $ amazone link
1 Water pump 7.39 $ amazone link
1 Pipe 0.5$ from lab diameter 8 mm, ~ 50cm lenght
1 Silicone sealant 5.00 $ from lab
1 Filament ~15$ from lab
2 Copper Clad Laminate PCB 7x10cm 0.90 $ aliexpress link
1 Seeed XIAO RP2040 5.40 $ official page store
1 Acrylic sheet 5$ from lab 4.8mm thickness, 20x20cm
Electronics components ~ 5$ from lab
Total 45.99$

Licensing

MOI by Elen Grigoryan is licensed under CC BY-NC-SA 4.0

Source files