<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shilleh</title>
    <description>The latest articles on DEV Community by Shilleh (@shilleh).</description>
    <link>https://dev.to/shilleh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1045503%2Ffc861dbf-7606-4dba-8f85-387ca9a406a8.JPG</url>
      <title>DEV Community: Shilleh</title>
      <link>https://dev.to/shilleh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shilleh"/>
    <language>en</language>
    <item>
      <title>Arduino Nano A4988: Mini CNC Pen Plotter</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:38:52 +0000</pubDate>
      <link>https://dev.to/shilleh/arduino-nano-a4988-mini-cnc-pen-plotter-5a5b</link>
      <guid>https://dev.to/shilleh/arduino-nano-a4988-mini-cnc-pen-plotter-5a5b</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Arduino Nano + A4988 stepper drivers + MG90S servo pen lift:&lt;/strong&gt; Build a mini CNC pen plotter where two steppers move the pen on X and Y, and a servo lifts and lowers the pen so you can draw from G-code streamed over USB.&lt;/p&gt;

&lt;p&gt;Send G-code from your computer and the Arduino will draw anything from logos to circuit diagrams to your kid's name. It is a practical entry point to real CNC with a low parts cost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~4 hours (build) + ~1 hour (software setup)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Advanced&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A 2-axis pen plotter driven by G-code from your PC, capable of A6-size drawings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe3zaniwhmuosbix5iiw5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe3zaniwhmuosbix5iiw5.png" alt="Arduino Nano mini CNC pen plotter with two stepper motors, two A4988 drivers, and a servo pen lift" width="800" height="667"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Two stepper motors + servo + Arduino = CNC plotter under $30.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-original-a4988-stepper-motor-driver-with-heatsink" rel="noopener noreferrer"&gt;2× A4988 Stepper Driver&lt;/a&gt; - drives each stepper motor from Arduino STEP/DIR signals&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/mg90s-metal-gear-micro-servo-motor-180-degree-9g-for-rc-plane" rel="noopener noreferrer"&gt;MG90S Servo (pen lift)&lt;/a&gt; - lifts and lowers the pen between strokes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/arduino-nano-v3-presoldered-ch340g-atmega328p" rel="noopener noreferrer"&gt;Arduino Nano V3.0 Pre-Soldered&lt;/a&gt; - runs GRBL and outputs step/dir and servo control&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;120 PCS Dupont Jumper Wires&lt;/a&gt; - quick, reliable interconnects for prototyping and wiring&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;2× NEMA 17 (or smaller) stepper motors&lt;/li&gt;
&lt;li&gt;2× linear rails + carriages (or scavenged from old DVD drives)&lt;/li&gt;
&lt;li&gt;Pen + holder + 12V DC supply&lt;/li&gt;
&lt;li&gt;3D-printed or laser-cut frame&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: This is an entry point to real CNC. Once you have a working pen plotter, you understand 90% of what is needed for a laser engraver or 3D printer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 - Build the Frame
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Create a rigid base that holds the two axis rails square to each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Assemble the frame so the X and Y rails sit perpendicular and the carriages can move without binding. Keep everything aligned before tightening hardware.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0k0wmvk1fs7l4p0d72hk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0k0wmvk1fs7l4p0d72hk.png" alt="Mini CNC pen plotter frame build with perpendicular linear rails for X and Y motion" width="800" height="724"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Two perpendicular linear rails: X moves the pen, Y moves the paper carriage.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The carriages slide smoothly by hand across the full travel on both axes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - Set Up the Axes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Convert stepper rotation into repeatable linear movement on both axes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Mount each stepper, align the pulley and belt, and connect the belt to the moving carriage. Adjust belt tension so it is firm but not overstretched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv009qo2maqdmmin7p8ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv009qo2maqdmmin7p8ec.png" alt="X and Y axis mechanics showing stepper motor pulley belt and carriage on a mini CNC pen plotter" width="800" height="669"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each axis: stepper - toothed pulley - belt - carriage. Smooth, repeatable motion.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Turning the stepper pulley by hand moves the carriage smoothly with minimal backlash.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Wire Electronics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the Arduino Nano to two A4988 drivers and the pen-lift servo, then share power and ground correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire STEP and DIR from the Arduino to each A4988, connect the servo signal to the Arduino, and power both drivers from the 12V supply. Ensure the Arduino ground and the 12V supply ground are common.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqhn6fw1vsu4ttepgvhrw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqhn6fw1vsu4ttepgvhrw.png" alt="Wiring diagram of Arduino Nano connected to two A4988 stepper drivers and a servo with shared 12V motor supply and common ground" width="800" height="672"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each A4988 takes STEP + DIR from the Arduino, and motor power from a 12V supply.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;X-axis A4988: STEP - D2, DIR - D5&lt;/li&gt;
&lt;li&gt;Y-axis A4988: STEP - D3, DIR - D6&lt;/li&gt;
&lt;li&gt;Servo signal - D11 (pen up/down)&lt;/li&gt;
&lt;li&gt;Both A4988s share VMOT - 12V, EN - GND, common Arduino + supply ground&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Your wiring matches the pin mapping above, and all grounds are tied together (Arduino ground to motor supply ground).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Pen Mechanism
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Add a reliable pen lift so the plotter can move between strokes without drawing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Mount the MG90S servo and link it to the pen holder so the pen can move up and down freely. Use the intended angles for up/down movement as you configure GRBL/your workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkssnz6lijvq67mhy1tzk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkssnz6lijvq67mhy1tzk.png" alt="MG90S servo pen lift mechanism mounted on a mini CNC pen plotter pen holder" width="800" height="446"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Servo lifts the pen between strokes: write 0° to drop pen, write 90° to lift.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The servo can lift the pen cleanly off the page and lower it back down without sticking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Flash GRBL
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Install GRBL so the Arduino Nano can accept and execute G-code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Flash &lt;strong&gt;GRBL 1.1f&lt;/strong&gt; firmware to the Arduino. On your PC, use &lt;strong&gt;Universal Gcode Sender (UGS)&lt;/strong&gt; to stream G-code over USB. UGS includes a tool to convert SVG to G-code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; UGS connects to the Arduino over USB and you can send commands/jobs to the controller.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Draw Something
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Run your first real G-code drawing to validate mechanics, wiring, and workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Load a simple G-code file (or convert an SVG in UGS) and run it at conservative speeds until you confirm the axes move correctly and the pen lift timing is right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiwxbdnc1mtvfdcbj0eus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiwxbdnc1mtvfdcbj0eus.png" alt="Finished drawing created by an Arduino Nano GRBL mini CNC pen plotter on paper" width="800" height="560"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;From SVG to paper: first prints are crude, the 5th is impressive.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The plotter produces a recognizable drawing, and the quality improves as you refine alignment and settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 - Where to Take It Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify practical upgrades and follow-on projects based on the same CNC fundamentals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Choose one path below and plan your next iteration using the same G-code and motion-control concepts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swap pen for a 500mW laser - small laser engraver&lt;/li&gt;
&lt;li&gt;Add a Z axis with a third A4988 + stepper - 3-axis CNC&lt;/li&gt;
&lt;li&gt;Upgrade to TMC2209 stepper drivers for silent motion&lt;/li&gt;
&lt;li&gt;Build a touchscreen UI for offline G-code playback (ILI9341 + ESP32)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a clear next upgrade direction after validating the core plotter build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This mini CNC pen plotter uses an Arduino Nano with A4988 stepper drivers and a servo pen lift to turn G-code into real drawings on paper. Once you understand the workflow here, the same concepts transfer directly to other CNC platforms.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>robotics</category>
      <category>diy</category>
      <category>electronics</category>
    </item>
    <item>
      <title>ESP32 DHT22: Automate Greenhouse Fan and Pump</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:36:00 +0000</pubDate>
      <link>https://dev.to/shilleh/esp32-dht22-automate-greenhouse-fan-and-pump-3p8g</link>
      <guid>https://dev.to/shilleh/esp32-dht22-automate-greenhouse-fan-and-pump-3p8g</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ESP32 smart greenhouse with DHT22 + soil moisture sensor:&lt;/strong&gt; In this build, an ESP32 reads air temperature and humidity from a DHT22 and soil moisture from an analog probe, then switches a fan and water pump using a 2-channel relay based on simple thresholds.&lt;/p&gt;

&lt;p&gt;You will also output live readings over Serial, and you can optionally stream the data to a phone dashboard (Blynk) or MQTT for remote monitoring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~2 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; An ESP32 monitoring air + soil, auto-controlling fan + pump, and streaming readings to Serial (with optional Blynk/MQTT dashboard).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9a84pekxk0l76moje96.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9a84pekxk0l76moje96.jpg" alt="ESP32 smart greenhouse project cover showing sensors and relay-controlled outputs" width="800" height="667"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Smart greenhouse: sense, decide, act, broadcast.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-dht22-with-cables" rel="noopener noreferrer"&gt;DHT22 Temperature &amp;amp; Humidity Sensor&lt;/a&gt; - measures greenhouse air temperature and humidity.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/soil-moisture-sensor-hygrometer-module-for-arduino-watering-kit" rel="noopener noreferrer"&gt;Soil Moisture Sensor&lt;/a&gt; - provides an analog moisture reading for watering decisions.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/2-channel-5v-relay-module" rel="noopener noreferrer"&gt;2-Channel 5V Relay Module&lt;/a&gt; - switches the pump and fan from ESP32 GPIO pins.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/esp32-wroom-dev-board-cp2102-usb-c-presoldered" rel="noopener noreferrer"&gt;ESP32-WROOM Dev Board (USB-C)&lt;/a&gt; - main controller with Wi-Fi and analog input.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;120 PCS Dupont Jumper Wires&lt;/a&gt; - makes quick sensor and relay connections.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-400-point-breadboard" rel="noopener noreferrer"&gt;400-Point Breadboard&lt;/a&gt; - simplifies prototyping before a permanent build.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;5V mini water pump + tubing&lt;/li&gt;
&lt;li&gt;12V cooling fan&lt;/li&gt;
&lt;li&gt;12V power supply for fan + pump&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: Always common-ground the microcontroller (ESP32/Arduino), relay module, and the relay-switched external supply.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - Wire the Sensors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the DHT22 and the soil moisture module to the ESP32 so you can read air and soil conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire the DHT22 data line to GPIO 4 (D4). Wire the soil moisture analog output to the ESP32 analog input on GPIO 36. Provide correct power and ground to both sensors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hp0lbiumh14rtwmhsyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hp0lbiumh14rtwmhsyu.png" alt="ESP32 wired on a breadboard to a DHT22 on GPIO 4 and a soil moisture sensor analog output on GPIO 36" width="800" height="337"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;DHT22 on D4, soil moisture on A0 (GPIO 36 on ESP32).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Your ESP32 has both sensors physically connected and ready for code.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Add Relays for the Pump and Fan
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Add two relay channels so the ESP32 can switch a water pump and a cooling fan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Connect relay IN1 to GPIO 26 for the pump and relay IN2 to GPIO 27 for the fan. Wire your pump and fan through the relay contacts appropriate for your power setup, and ensure all grounds are common between the ESP32, relay module, and the external supply.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkv8om6rj6o4xa7t24biu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkv8om6rj6o4xa7t24biu.png" alt="Wiring diagram showing ESP32 connected to a 2-channel relay with IN1 on GPIO 26 for pump and IN2 on GPIO 27 for fan" width="800" height="162"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;2-channel relay: IN1 to GPIO 26 (pump), IN2 to GPIO 27 (fan).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The ESP32 can control both relay inputs, and the pump and fan are ready to be switched by the relays.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - Upload the Sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Flash the ESP32 with code that reads the DHT22 and soil sensor, then switches the pump and fan based on thresholds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Update the Wi-Fi credentials in the sketch, compile, and upload to your ESP32. Open Serial Monitor at 115200 baud to view temperature, humidity, and soil readings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WiFi.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;DHT.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#define DHTPIN 4
#define DHTTYPE DHT22
#define SOIL_PIN 36
#define PUMP 26
#define FAN 27
&lt;/span&gt;&lt;span class="n"&gt;DHT&lt;/span&gt; &lt;span class="nf"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DHTPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DHTTYPE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PUMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"YOUR_SSID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"YOUR_PASS"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readTemperature&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readHumidity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;soil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SOIL_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Lower soil reading = drier. Threshold ~2500/4095 for "dry".&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PUMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soil&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"T=%.1fC H=%.1f%% soil=%d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;soil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Every 5 seconds you see T/H/soil values in Serial, and the relays switch when the soil reading and temperature cross the set thresholds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - See It Run
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Verify the automation logic with real sensor changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Test the soil sensor by changing moisture conditions and confirm the pump relay reacts. Warm the temperature sensor area (carefully) and confirm the fan relay triggers around the 30 C threshold used in the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foo86hjicxa5xyhxgv4ku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foo86hjicxa5xyhxgv4ku.png" alt="ESP32 smart greenhouse running with relay module active as soil dries and fan control based on temperature" width="799" height="437"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Relays click as soil dries; fan kicks in past 30 C.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You can hear or see relay activity when conditions change, and Serial output confirms the readings and decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Where to Take It Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Extend the project with dashboards, logging, and extra sensors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Consider these optional upgrades:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Push readings to Blynk for a phone dashboard&lt;/li&gt;
&lt;li&gt;Add a BH1750 light sensor for grow-light scheduling&lt;/li&gt;
&lt;li&gt;Log to ThingSpeak for long-term trend graphs&lt;/li&gt;
&lt;li&gt;Add LoRa for an off-grid garden tower&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A clear plan for expanding the ESP32 greenhouse controller beyond the basic automation loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built an ESP32 smart greenhouse controller that reads a DHT22 and soil moisture sensor, then switches a fan and pump through a 2-channel relay based on simple thresholds. This setup scales from a small plant enclosure to larger indoor garden monitoring with dashboards and logging.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>iot</category>
      <category>diy</category>
      <category>electronics</category>
    </item>
    <item>
      <title>Arduino 24MHz Logic Analyzer: Decode I2C, SPI, UART</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:33:26 +0000</pubDate>
      <link>https://dev.to/shilleh/arduino-24mhz-logic-analyzer-decode-i2c-spi-uart-4o5n</link>
      <guid>https://dev.to/shilleh/arduino-24mhz-logic-analyzer-decode-i2c-spi-uart-4o5n</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Arduino + 24MHz USB Logic Analyzer:&lt;/strong&gt; In this guide, you use an Arduino (Nano) and a 24MHz 8-channel USB logic analyzer to capture an I2C transaction and decode it in PulseView, so you can stop debugging SPI, I2C, UART, or 1-Wire blind.&lt;/p&gt;

&lt;p&gt;The 24MHz / 8-channel USB analyzer is a low-cost option that works with PulseView/sigrok and Saleae Logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~25 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Beginner / Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; An Arduino I2C transaction captured live and decoded in PulseView.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovpa929n5a4kp5lty3rw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovpa929n5a4kp5lty3rw.jpg" alt="24MHz 8-channel USB logic analyzer used to capture Arduino I2C, SPI, and UART signals" width="799" height="666"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The 24MHz USB logic analyzer - 8 channels, USB powered, $10.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/24mhz-8-channel-usb-logic-analyzer-digital-debugger-for-arduino" rel="noopener noreferrer"&gt;24MHz 8-Channel USB Logic Analyzer&lt;/a&gt; - captures and decodes digital buses like I2C, SPI, and UART.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/arduino-nano-v3-presoldered-ch340g-atmega328p" rel="noopener noreferrer"&gt;Arduino Nano V3.0 Pre-Soldered&lt;/a&gt; - easy target to generate I2C traffic to probe.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;120 PCS Dupont Jumper Wires&lt;/a&gt; - quick connections from analyzer leads to Arduino pins.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A computer running &lt;a href="https://sigrok.org/wiki/PulseView" rel="noopener noreferrer"&gt;PulseView&lt;/a&gt; (free) or Saleae Logic&lt;/li&gt;
&lt;li&gt;A target circuit to probe (any Arduino + I2C sensor works)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: 24MHz is fast enough for SPI up to ~10MHz, I2C at any speed, UART up to ~115200 baud, and most slow digital protocols. Not enough for high-speed SDIO or 100MHz QSPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 - Inspect the Analyzer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify the connector and channel pins so you know what to hook up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Locate the USB-Mini connector and the 10-pin header. The header typically provides 8 channels plus two ground pins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqg3xj5ulrkufa9a53uaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqg3xj5ulrkufa9a53uaz.png" alt="Top view of a 24MHz USB logic analyzer showing USB-Mini connector and 10-pin header for 8 channels and ground" width="287" height="42"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;USB-Mini on one side, 10 pins on the other: 8 channels + GND + GND.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You can point to the channel pins and at least one GND pin before wiring anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - Install PulseView
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Install capture software and the firmware support needed for the analyzer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Download and install PulseView from sigrok.org. It includes the &lt;strong&gt;fx2lafw&lt;/strong&gt; firmware needed by the CY7C68013A chip inside many of these analyzers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmmh5tyjkppx08oqwxldy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmmh5tyjkppx08oqwxldy.jpg" alt="PulseView software capturing digital traces from a 24MHz USB logic analyzer" width="720" height="1464"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;PulseView decodes I2C / SPI / UART out of the box.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; PulseView launches and is ready to connect to the logic analyzer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Probe an I2C Transaction
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Capture SDA and SCL while your Arduino talks to an I2C device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire any I2C sensor (BME280, MPU6050, OLED) to your Arduino. Then connect the analyzer leads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analyzer GND to Arduino GND&lt;/li&gt;
&lt;li&gt;Channel 0 to SDA (Arduino A4)&lt;/li&gt;
&lt;li&gt;Channel 1 to SCL (Arduino A5)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In PulseView, set the sample rate to 8 MS/s and set total samples to 1 M, then click Start. Run your Arduino sketch to generate I2C traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You see two digital traces toggling (SDA and SCL) during I2C activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Decode
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Turn the raw SDA/SCL waveforms into readable I2C bytes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; In PulseView, right-click on the channels and choose &lt;strong&gt;Add decoder → I2C&lt;/strong&gt;. Assign which channel is SDA and which is SCL. PulseView will annotate addresses and data on top of the captured signals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8py4emp3yfsyajith3mm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8py4emp3yfsyajith3mm.jpg" alt="PulseView displaying decoded I2C address and data bytes from an Arduino captured with a 24MHz USB logic analyzer" width="800" height="1422"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Each I2C address byte, register byte, and data byte appears decoded above the raw lines.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You can read every byte your Arduino writes or reads over I2C directly in the capture timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Where to Take It Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Apply the same capture-and-decode workflow to other digital protocols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Use the analyzer on other buses and modules you work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture SPI traffic to confirm a sensor library is sending what you think&lt;/li&gt;
&lt;li&gt;Decode UART at the wire level when Serial Monitor lies&lt;/li&gt;
&lt;li&gt;Verify NEC IR remote codes by probing the IR receiver output&lt;/li&gt;
&lt;li&gt;Reverse-engineer cheap modules whose datasheets are wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a repeatable workflow for validating digital communications with real signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A 24MHz USB logic analyzer paired with PulseView makes it practical to inspect Arduino I2C, SPI, and UART signals and decode real traffic in minutes. Once you can see the bus, debugging turns from guesswork into verification.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or need embedded debugging and reverse-engineering support, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Credits: The USB logic analyzer photos and PulseView screenshots in this tutorial are credited to &lt;a href="https://www.instructables.com/FX2LP-CY7C68013A-USB-Dev-Board-Logic-Analyzer/" rel="noopener noreferrer"&gt;Instructables&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>embedded</category>
      <category>electronics</category>
      <category>debugging</category>
    </item>
    <item>
      <title>ESP32 BH1750: Motorized Wi-Fi Blinds Control</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:29:20 +0000</pubDate>
      <link>https://dev.to/shilleh/esp32-bh1750-motorized-wi-fi-blinds-control-54m3</link>
      <guid>https://dev.to/shilleh/esp32-bh1750-motorized-wi-fi-blinds-control-54m3</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ESP32 + BH1750 smart blinds:&lt;/strong&gt; Use an ESP32 with a BH1750 light sensor and a 28BYJ-48 stepper motor to roll your blinds up at sunrise and down at sunset, with simple Wi-Fi phone control on your local network.&lt;/p&gt;

&lt;p&gt;A 28BYJ-48 stepper motor drives the blind's pull cord through a small pulley. The BH1750 reads ambient brightness, and the ESP32 exposes basic web endpoints so you can raise or lower the blinds from a phone on the same network.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~3 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; Wi-Fi controlled motorized blinds with light-aware automatic behavior (optional lux thresholds), plus an RTC option for backup scheduling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwxivd7rxror25ovmg0qs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwxivd7rxror25ovmg0qs.jpg" alt="ESP32 motorized roller blinds using a 28BYJ-48 stepper motor and BH1750 light sensor" width="800" height="667"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;28BYJ-48 stepper + ULN2003 + ESP32 + BH1750 = smart blinds with local control.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-5v-4-phase-stepper-motor-with-uln2003-driver" rel="noopener noreferrer"&gt;28BYJ-48 Stepper + ULN2003 Driver&lt;/a&gt; - provides the geared motor and driver board to pull the blind cord from ESP32 GPIO pins.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/esp32-wroom-dev-board-cp2102-usb-c-presoldered" rel="noopener noreferrer"&gt;ESP32-WROOM Dev Board&lt;/a&gt; - runs the Wi-Fi web server and controls the stepper.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-gy-302-bh1750-pre-soldered-light-intensity-module" rel="noopener noreferrer"&gt;BH1750 Light Sensor&lt;/a&gt; - measures ambient brightness (lux) for optional auto open/close behavior.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/ds3231-at24c32-iic-module-precision-rtc-module-with-cr2032-battery" rel="noopener noreferrer"&gt;DS3231 RTC (backup schedule)&lt;/a&gt; - optional real-time clock for scheduling if you want time-based control.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;Dupont Jumper Wires&lt;/a&gt; - makes prototyping the wiring on a breadboard faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;3D-printed mounting bracket + pulley - holds the stepper next to the blind cord and transfers motion to the pull cord.&lt;/li&gt;
&lt;li&gt;5V power supply - powers the stepper driver and electronics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: The ULN2003 inputs connect to ESP32 GPIOs. The BH1750 and DS3231 share the I2C bus (SDA/SCL). Use a stable 5V supply sized for stepper load.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - The Mechanism
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Build a simple pulley and bracket so the 28BYJ-48 can pull the roller blind cord.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Fit a pulley to the stepper shaft and position the stepper next to the blind's pull cord. Ensure the cord tracks consistently on the pulley and does not slip during direction changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhopoqh01xnv9q2mtu6t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhhopoqh01xnv9q2mtu6t.jpg" alt="28BYJ-48 stepper motor with a 3D-printed pulley engaging a roller blind pull cord" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;3D-printed pulley on the stepper shaft engages the blind's pull cord.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Turning the stepper by hand (or briefly under power later) moves the blind cord smoothly without binding.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Wire It
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the ESP32 to the ULN2003 stepper driver and connect BH1750 (and optional DS3231) over I2C.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire the four ULN2003 input pins to ESP32 GPIOs 12 to 15 as shown. Connect the stepper motor to the ULN2003 motor header. Wire BH1750 to the ESP32 I2C pins (SDA/SCL) and power. If using DS3231, wire it to the same I2C bus and power.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3thbnjcz3urmjj9kktte.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3thbnjcz3urmjj9kktte.jpg" alt="ESP32 wired on a breadboard with ULN2003 stepper driver, BH1750 light sensor, and DS3231 RTC module" width="600" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ESP32 + ULN2003 driver + BH1750 + DS3231 on a breadboard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzo9l2v9m5mr0wabxeug2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzo9l2v9m5mr0wabxeug2.png" alt="Wiring diagram showing ESP32 GPIO 12-15 connected to ULN2003 inputs, plus BH1750 and DS3231 connected over I2C" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;4 stepper wires to ULN2003 to ESP32 GPIOs 12 to 15. BH1750 + DS3231 on I2C.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Your wiring matches the diagram, with the stepper controlled through the ULN2003 and sensors sharing the I2C bus.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - Sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Flash a simple ESP32 sketch that connects to Wi-Fi, reads BH1750 lux, and exposes /up and /down web endpoints to move the stepper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Update the Wi-Fi SSID and password, flash the code, and open the Serial Monitor to find the ESP32 IP on your network. Use a phone or browser on the same network to visit http:///up and http:///down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Stepper.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WiFi.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WebServer.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;BH1750.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Stepper&lt;/span&gt; &lt;span class="nf"&gt;stepper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BH1750&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;WebServer&lt;/span&gt; &lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stepper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="c1"&gt;// 10 turns to roll up&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;down&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stepper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2048&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"PASS"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;WL_CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;stepper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setSpeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// RPM&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/up"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="p"&gt;[](){&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"UP"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/down"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[](){&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DOWN"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;lux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readLightLevel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// Optional: auto-roll based on lux thresholds&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The ESP32 connects to Wi-Fi, and hitting /up or /down moves the blinds. BH1750 lux readings are available in code for optional automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Mount It
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Secure the motor and pulley so the mechanism can run reliably on the window frame.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Attach the bracket to the window frame near the blind cord path. Align the pulley so the cord runs straight and does not rub. Make sure the assembly is rigid enough that the motor does not twist under load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu3lhh0fyyx923x2jvqpl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu3lhh0fyyx923x2jvqpl.jpg" alt="Mounted 28BYJ-48 stepper bracket on a window frame pulling a roller blind cord" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Stepper bracket attached to the window frame; pulley grabs the blind cord.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The cord tracks cleanly and the motor stays aligned while the blinds move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Use It
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Control the blinds from a phone on your local Wi-Fi network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; On a device connected to the same network, navigate to the ESP32 IP address followed by /up or /down. If desired, adjust step counts and speed in the sketch to match your blind travel and torque needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjky8wfihk0zejgrkao4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjky8wfihk0zejgrkao4x.png" alt="Phone-controlled ESP32 motorized blinds rolling up and down via local web endpoints" width="800" height="608"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Visit the ESP32 IP with /up or /down from any phone on the network.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The blinds roll up and down on demand using the ESP32 web endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Where to Take It Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify safe, optional improvements you can add after the basic build works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Consider expanding the project with one of these additions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schedule via DS3231 - open at sunrise, close at sunset, no Wi-Fi needed&lt;/li&gt;
&lt;li&gt;Integrate with Home Assistant via MQTT for whole-house control&lt;/li&gt;
&lt;li&gt;Add a closed-state limit switch - never over-rotate the stepper&lt;/li&gt;
&lt;li&gt;Voice control via Alexa/Google through Home Assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a clear path to add scheduling, integrations, and safer end-stops without changing the core wiring concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built ESP32-controlled motorized blinds using a 28BYJ-48 stepper motor, ULN2003 driver, and a BH1750 light sensor, with simple /up and /down web control and the option to automate based on brightness.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Reference: Original project inspiration credited to &lt;a href="https://www.instructables.com/Arduino-Motorized-Roller-Blinds/" rel="noopener noreferrer"&gt;Instructables&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>iot</category>
      <category>smarthome</category>
      <category>diy</category>
    </item>
    <item>
      <title>ESP32 MQ-135: Air Quality Dashboard with OLED</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:26:30 +0000</pubDate>
      <link>https://dev.to/shilleh/esp32-mq-135-air-quality-dashboard-with-oled-36go</link>
      <guid>https://dev.to/shilleh/esp32-mq-135-air-quality-dashboard-with-oled-36go</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ESP32 + MQ-135 + DHT22 + SSD1306 OLED:&lt;/strong&gt; Build an indoor air quality dashboard where the MQ-135 tracks gas and smoke trends, the DHT22 measures temperature and humidity, and an OLED plus Wi-Fi web page shows the readings live.&lt;/p&gt;

&lt;p&gt;This gives you a quick, desk-friendly way to spot stale indoor air without relying on a cloud app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~1.5 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A desk-mounted air quality station with OLED + Wi-Fi dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06ldkrz4s7kt8rppy7vp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06ldkrz4s7kt8rppy7vp.jpg" alt="ESP32 air quality dashboard with MQ-135 gas sensor, DHT22 temperature humidity sensor, and SSD1306 OLED display" width="751" height="617"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MQ-135 + DHT22 + OLED + ESP32 = complete air quality station.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/mq-135-air-quality-hazardous-gas-sensor-module-for-arduino" rel="noopener noreferrer"&gt;MQ-135 Air Quality Gas Sensor&lt;/a&gt; - provides an analog air quality trend signal (CO2/NOx/ammonia/smoke sensitivity).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-dht22-with-cables" rel="noopener noreferrer"&gt;DHT22 Temperature &amp;amp; Humidity Sensor&lt;/a&gt; - measures ambient temperature and humidity.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/0-96-i2c-blue-oled-display-module-4-pin-ssd1306" rel="noopener noreferrer"&gt;0.96" I²C SSD1306 OLED&lt;/a&gt; - shows live readings locally on the device.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/esp32-wroom-dev-board-cp2102-usb-c-presoldered" rel="noopener noreferrer"&gt;ESP32-WROOM Dev Board&lt;/a&gt; - reads sensors and hosts a simple web dashboard over Wi-Fi.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;120 PCS Dupont Jumper Wires&lt;/a&gt; - makes breadboard wiring quick and reliable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;USB power supply&lt;/li&gt;
&lt;li&gt;Wi-Fi network&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: MQ-135 needs about 24 hours of burn-in the first time you power it up. Readings stabilise after that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - Gather the components
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Make sure you have the ESP32, MQ-135, DHT22, and SSD1306 OLED ready before wiring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Lay out the parts on your bench and confirm you have jumper wires and USB power for the ESP32.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwso59eyatuerh2mt18kv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwso59eyatuerh2mt18kv.jpg" alt="ESP32 air quality dashboard components including MQ-135 gas sensor module, DHT22 sensor, and SSD1306 OLED ready for wiring" width="760" height="617"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Three sensors, one ESP32. Everything fits on a small breadboard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All modules are identified and ready to connect.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Wire the MQ-135, DHT22, and OLED to the ESP32
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect each module to the correct ESP32 pins for analog input, a digital GPIO, and I2C.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire the MQ-135 analog output (AOUT) to GPIO 36. Wire the DHT22 data pin to GPIO 4. Connect the OLED on I2C using GPIO 21 (SDA) and GPIO 22 (SCL). Provide power and ground to each module as required by your boards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo1g39gjvceu5aicatvq0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo1g39gjvceu5aicatvq0.jpg" alt="Wiring diagram showing ESP32 connected to MQ-135 AOUT on GPIO 36, DHT22 on GPIO 4, and SSD1306 OLED over I2C on GPIO 21 and GPIO 22" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MQ-135 AOUT to GPIO 36, DHT22 to GPIO 4, OLED on I2C (GPIO 21/22).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The full circuit is wired and ready to power on without loose connections.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - Assemble the station on the breadboard
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Turn the wired modules into a stable, desk-ready build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Mount the ESP32, MQ-135, DHT22, and OLED on a breadboard so the wiring is secure and the OLED is visible from the front.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frq1fti3ex5mu8zo5t1hw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frq1fti3ex5mu8zo5t1hw.jpg" alt="Assembled ESP32 air quality station on a breadboard with MQ-135 gas sensor, DHT22, and SSD1306 OLED connected" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;All three modules + ESP32 on one board.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A compact build that can sit on a desk and run from USB power.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4 - Upload the sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Program the ESP32 to read the MQ-135 analog value, read temperature and humidity from the DHT22, display results on the OLED, and serve a basic web dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; In your Arduino IDE (or compatible ESP32 toolchain), paste the sketch below. Update &lt;code&gt;SSID&lt;/code&gt; and &lt;code&gt;PASS&lt;/code&gt; with your Wi-Fi credentials, then compile and upload to the ESP32.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WiFi.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WebServer.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;DHT.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_SSD1306.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;DHT&lt;/span&gt; &lt;span class="nf"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DHT22&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Adafruit_SSD1306&lt;/span&gt; &lt;span class="nf"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;WebServer&lt;/span&gt; &lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;MQ_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;aq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"&amp;lt;h1&amp;gt;Air Quality&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;T: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" C&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;H: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s"&gt;"%&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;AQ: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;/p&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSD1306_SWITCHCAPVCC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x3C&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"PASS"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;WL_CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readTemperature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readHumidity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;aq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MQ_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clearDisplay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setTextSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setTextColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSD1306_WHITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"T: %.1f C&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;H: %.1f%%&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;AQ: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localIP&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;c_str&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handleClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The ESP32 connects to Wi-Fi, the OLED updates every couple seconds, and the serial monitor shows normal startup output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - View the OLED and open the web dashboard
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Confirm the readings appear locally on the OLED and remotely in a browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Power the device and check the OLED for temperature, humidity, air quality value (analog reading), and the ESP32 IP address. On a phone or computer on the same Wi-Fi network, open the ESP32 IP address in a browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fste3iy1dnr1y3dckg3eg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fste3iy1dnr1y3dckg3eg.jpg" alt="SSD1306 OLED displaying live temperature humidity and MQ-135 air quality value from an ESP32" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;OLED shows live T / H / AQ; phone shows same on the dashboard URL.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5mkluelet98xxlsml9h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl5mkluelet98xxlsml9h.jpg" alt="Phone browser showing the ESP32 web dashboard with temperature humidity and MQ-135 air quality values" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Open the ESP32's IP in any browser on your network for the live readout.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You see live values on the OLED and the same values on the web page served by the ESP32.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Extend the project
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Plan safe, practical next upgrades once the base station works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Pick one improvement and implement it after your baseline readings are stable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log readings to ThingSpeak for long-term graphs&lt;/li&gt;
&lt;li&gt;Trigger a relay-controlled fan above AQ threshold&lt;/li&gt;
&lt;li&gt;Add a second MQ sensor for CO specifically (MQ-7)&lt;/li&gt;
&lt;li&gt;Send a Telegram alert when CO2 exceeds a danger threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a clear path for turning a simple dashboard into a more complete monitoring system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built an ESP32 air quality dashboard that reads an MQ-135 for air quality trends, uses a DHT22 for temperature and humidity, and shows everything on an SSD1306 OLED plus a Wi-Fi web page.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Credit: This build is based on a reference guide from &lt;a href="https://www.instructables.com/ESP32-MQ135-Gas-SensorAir-Quality-Index/" rel="noopener noreferrer"&gt;Instructables&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>iot</category>
      <category>electronics</category>
      <category>diy</category>
    </item>
    <item>
      <title>Arduino RC522 RFID: Smart Door Lock with OLED</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:23:46 +0000</pubDate>
      <link>https://dev.to/shilleh/arduino-rc522-rfid-smart-door-lock-with-oled-pf9</link>
      <guid>https://dev.to/shilleh/arduino-rc522-rfid-smart-door-lock-with-oled-pf9</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Arduino Nano + RC522 RFID smart door lock:&lt;/strong&gt; Combine an RC522 RFID reader, a servo, and a 0.96 inch SSD1306 OLED to build a tap-to-unlock door system where authorized cards unlock the latch and the OLED shows status messages.&lt;/p&gt;

&lt;p&gt;Tap a registered card or fob and the servo throws a latch while the OLED prints "ACCESS GRANTED". Tap an unknown card and the OLED prints "ACCESS DENIED".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~1 hour&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Beginner / Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; An RFID-driven door lock with an OLED status display.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgvq5fzjx3f1tieiemyq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgvq5fzjx3f1tieiemyq8.png" alt="Arduino Nano RC522 RFID smart door lock build with servo latch and 0.96 inch OLED status display" width="800" height="667"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;RC522 + servo + OLED + Arduino Nano for a complete access-control build.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/rc522-rfid-reader-writer-module-13-56mhz-spi-kit-for-arduino" rel="noopener noreferrer"&gt;RC522 RFID Reader Writer Kit (with card + fob)&lt;/a&gt; - reads 13.56 MHz RFID card UIDs over SPI.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/mg90s-metal-gear-micro-servo-motor-180-degree-9g-for-rc-plane" rel="noopener noreferrer"&gt;MG90S Metal-Gear Servo&lt;/a&gt; - actuates the latch for lock and unlock motion.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/0-96-i2c-blue-oled-display-module-4-pin-ssd1306" rel="noopener noreferrer"&gt;0.96 inch I2C SSD1306 OLED&lt;/a&gt; - displays "ACCESS GRANTED" and "ACCESS DENIED".&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/arduino-nano-v3-presoldered-ch340g-atmega328p" rel="noopener noreferrer"&gt;Arduino Nano V3.0 Pre-Soldered&lt;/a&gt; - runs the RFID read, UID check, OLED messages, and servo control.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/13-56mhz-rfid-key-fob-tag-ic-s50-token-iso14443a-access-control" rel="noopener noreferrer"&gt;Extra 13.56 MHz Key Fobs&lt;/a&gt; - add more users to your lock.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;120 PCS Dupont Jumper Wires&lt;/a&gt; - makes breadboard wiring quick and clean.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-400-point-breadboard" rel="noopener noreferrer"&gt;400-Point Breadboard&lt;/a&gt; - prototyping the full circuit before mounting.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A door, drawer, or box you want to lock&lt;/li&gt;
&lt;li&gt;3D-printed or wood servo bracket (optional but recommended)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: The RC522 is 3.3 V on the logic side. Power VCC from 3.3 V, not 5 V. The servo runs on 5 V.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - Lay Out the Modules
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify each module and confirm you have all the core parts before wiring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Place the Arduino Nano, RC522 RFID module, MG90S servo, and SSD1306 OLED on your work surface. If you are using a breadboard, position parts so wiring to SPI pins, I2C pins, and the servo PWM pin is easy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fssqess3j5ihymrypdmxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fssqess3j5ihymrypdmxz.png" alt="Arduino Nano, RC522 RFID module, MG90S servo, and SSD1306 OLED laid out before wiring the smart door lock" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;RFID reader, servo, OLED, and Arduino Nano are the four main pieces of the access system.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All modules are ready and oriented so you can wire them without crossing too many jumpers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Wire the RFID Reader (SPI)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the RC522 to the Arduino Nano over SPI so the Nano can read card UIDs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire the RC522 using the standard SPI connections plus RST and CS (SDA on many RC522 boards is the CS pin).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhzenptwhns16hqv5kcaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhzenptwhns16hqv5kcaz.png" alt="RC522 RFID reader wired to Arduino Nano using SPI pins D11 D12 D13 with CS on D10 and RST on D9" width="800" height="456"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;SPI hookup for RC522 with RST and CS lines.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RC522 VCC → 3.3 V (NOT 5 V)&lt;/li&gt;
&lt;li&gt;RC522 GND → GND&lt;/li&gt;
&lt;li&gt;RST → D9, SDA(CS) → D10&lt;/li&gt;
&lt;li&gt;MOSI → D11, MISO → D12, SCK → D13&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The RC522 is powered from 3.3 V and shares the SPI bus pins correctly with the Arduino Nano.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - Wire the Servo and OLED
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the servo for latch movement and the OLED for status messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire the servo signal to a PWM-capable pin and connect servo power to 5 V and GND. Then wire the SSD1306 OLED via I2C on A4 (SDA) and A5 (SCL).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk00h5ryjyweyo3j4uzpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk00h5ryjyweyo3j4uzpf.png" alt="MG90S servo wired to Arduino Nano with signal on D6 and power on 5V and GND for the door lock latch" width="800" height="472"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Servo signal on D6 (PWM-capable), 5 V and GND for power.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstkz14e6bhsl17171pks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstkz14e6bhsl17171pks.png" alt="SSD1306 0.96 inch OLED wired to Arduino Nano I2C with SDA on A4 and SCL on A5 for access status text" width="800" height="495"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;OLED on I2C with SDA to A4 and SCL to A5.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Servo: signal → D6, V+ → 5 V, GND → GND&lt;/li&gt;
&lt;li&gt;OLED: VCC → 5 V, GND → GND, SDA → A4, SCL → A5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The servo has a PWM control line and power, and the OLED is on the I2C bus at the typical address used by SSD1306 modules.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4 - Full Wiring Diagram
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Verify the complete wiring matches the intended circuit before uploading code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Compare your connections to the diagram. Confirm RC522 VCC is on 3.3 V, the OLED is on A4/A5, and all grounds are common.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbx0y2ecnm9esbo2ewab5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbx0y2ecnm9esbo2ewab5.png" alt="Full breadboard wiring diagram showing Arduino Nano connected to RC522 via SPI, SSD1306 OLED via I2C, and MG90S servo via PWM" width="800" height="384"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Complete circuit: SPI for RC522, I2C for OLED, and PWM for the servo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Your wiring matches the diagram with correct voltages and shared ground.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5 - Upload the Sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Program the Arduino Nano to read RFID tags, compare UIDs, move the servo, and show OLED messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; In the Arduino IDE, install these libraries: &lt;strong&gt;MFRC522&lt;/strong&gt;, &lt;strong&gt;Adafruit SSD1306&lt;/strong&gt;, &lt;strong&gt;Adafruit GFX&lt;/strong&gt;, and the built-in &lt;strong&gt;Servo&lt;/strong&gt;. Then paste and upload the sketch below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fapvo544dqvbsczbvkkut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fapvo544dqvbsczbvkkut.png" alt="Arduino IDE showing RFID smart door lock sketch that reads RC522 UID, drives servo, and writes access status to SSD1306 OLED" width="189" height="86"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One sketch combines RFID read, UID compare, servo control, and OLED status.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SPI.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;MFRC522.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_SSD1306.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Servo.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;RST_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SS_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVO_PIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;MFRC522&lt;/span&gt; &lt;span class="nf"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SS_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RST_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Adafruit_SSD1306&lt;/span&gt; &lt;span class="nf"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Servo&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Replace with the UID(s) of your authorized cards&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;AUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;[][&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mh"&gt;0xDE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xAD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xBE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xEF&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;line1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;line2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clearDisplay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setTextSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setTextColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSD1306_WHITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PCD_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSD1306_SWITCHCAPVCC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x3C&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SERVO_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ShillehTek"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Tap card..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;isAuthorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PICC_IsNewCardPresent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PICC_ReadCardSerial&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isAuthorized&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uidByte&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ACCESS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GRANTED"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ACCESS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DENIED"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ShillehTek"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Tap card..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PICC_HaltA&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PCD_StopCrypto1&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To register a card: run the &lt;strong&gt;ReadUIDSimple&lt;/strong&gt; example from the MFRC522 library, tap each card, note the 4 UID bytes, and paste them into the &lt;code&gt;AUTHORIZED&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The OLED shows "Tap card..." and the system reacts differently to authorized versus unauthorized tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Tap and Watch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Confirm RFID reads work and the lock behavior matches "granted" and "denied".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Tap an authorized card or fob to the RC522, then tap an unauthorized one. Watch the OLED and the servo position.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7asdk85v8q3ly515dlc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7asdk85v8q3ly515dlc.png" alt="13.56 MHz RFID cards and key fobs used to register users for the Arduino Nano RC522 door lock" width="512" height="512"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;13.56 MHz cards and fobs can be registered for multiple users.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqaizl57lmo32n44wa409.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqaizl57lmo32n44wa409.png" alt="OLED showing ACCESS GRANTED while MG90S servo rotates to unlock the latch in the Arduino Nano RFID door lock" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;"ACCESS GRANTED" displays while the servo throws the latch.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Authorized tags show "ACCESS GRANTED" and move the servo for about 3 seconds, and unauthorized tags show "ACCESS DENIED".&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 - Where to Take It Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify safe extensions you can add after the basic lock is working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; If you want to expand the project, consider these options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log every tap (UID + timestamp) to a microSD for attendance or an audit trail&lt;/li&gt;
&lt;li&gt;Push events over Wi-Fi (D1 Mini / ESP-01) to a server for cloud logging&lt;/li&gt;
&lt;li&gt;Add a 4x4 keypad for two-factor access (card + PIN)&lt;/li&gt;
&lt;li&gt;Replace the servo with a 12 V solenoid lock via a 1-channel relay for real doors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a clear list of next upgrades without changing the core wiring and logic you built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built an Arduino Nano smart door lock using an RC522 RFID reader, an MG90S servo for the latch, and an SSD1306 OLED to show access status. With one sketch, the system reads a tag UID, decides if it is authorized, and then unlocks or denies access.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Credits: The smart door lock photos and code in this tutorial are credited to &lt;a href="https://www.instructables.com/RFID-Smart-Door-Lock-With-OLED/" rel="noopener noreferrer"&gt;Instructables&lt;/a&gt;, which served as the reference for this ShillehTek version.&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>electronics</category>
      <category>diy</category>
      <category>security</category>
    </item>
    <item>
      <title>Arduino PCA9685: Joystick-Controlled 6-DOF Robot Arm</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:20:52 +0000</pubDate>
      <link>https://dev.to/shilleh/arduino-pca9685-joystick-controlled-6-dof-robot-arm-1p53</link>
      <guid>https://dev.to/shilleh/arduino-pca9685-joystick-controlled-6-dof-robot-arm-1p53</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Arduino Nano + PCA9685 servo driver robotic arm:&lt;/strong&gt; Build a 6-axis robotic arm controlled by a joystick, with six MG90S micro servos driven over I2C and an OLED that shows the current angles.&lt;/p&gt;

&lt;p&gt;Six MG90S micro servos give the arm 6 degrees of freedom: base rotation, shoulder, elbow, wrist twist, wrist tilt, and gripper. A 2-axis joystick maps to the most-used joints; the OLED shows current angles. The foundation of every robotics project - from animatronics to entry-level industrial pick-and-place.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~4 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Advanced&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A 6-DOF robotic arm with joystick control + saved positions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznn539q1x9j6cuafd7yx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznn539q1x9j6cuafd7yx.png" alt="6-DOF robotic arm build using an Arduino Nano, PCA9685 servo driver, and multiple MG90S servos" width="711" height="593"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;PCA9685 + 6x MG90S + joystick = 6-DOF robotic arm.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/pca9685-16-channel-12-bit-pwm-servo-motor-driver-i2c-for-arduino" rel="noopener noreferrer"&gt;PCA9685 16-Ch PWM Servo Driver&lt;/a&gt; - drives multiple servos from a single I2C interface&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/mg90s-metal-gear-micro-servo-motor-180-degree-9g-for-rc-plane" rel="noopener noreferrer"&gt;MG90S Metal-Gear Servo (x6)&lt;/a&gt; - provides the 6 joints of motion&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/ky-023-dual-axis-joystick-module-ps2-analog-sensor-for-arduino" rel="noopener noreferrer"&gt;KY-023 Joystick Module&lt;/a&gt; - analog input for manual joint control&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/0-96-i2c-blue-oled-display-module-4-pin-ssd1306" rel="noopener noreferrer"&gt;0.96" SSD1306 OLED&lt;/a&gt; - displays current joint angles&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/arduino-nano-v3-presoldered-ch340g-atmega328p" rel="noopener noreferrer"&gt;Arduino Nano V3.0&lt;/a&gt; - microcontroller for reading inputs and commanding servos&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;Dupont Jumper Wires&lt;/a&gt; - wiring between modules&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;3D-printed or laser-cut arm frame (open-source designs everywhere)&lt;/li&gt;
&lt;li&gt;5V/2-3A external supply for the servos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: Do not power 6 servos from the Arduino's 5V. They can pull ~700mA each at peak. Use a separate 5V supply with a common ground.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - Wire the PCA9685
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the Arduino Nano, PCA9685, joystick, OLED, and external 5V servo power safely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Connect the PCA9685 to the Arduino over I2C (SDA/SCL). Connect six MG90S servos to PCA9685 channels 0 to 5. Power the servos from a separate 5V supply connected to the PCA9685 V+ input, and make sure the external supply ground and Arduino ground are tied together (common ground).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4f8052inkx6d8ra8r2d6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4f8052inkx6d8ra8r2d6.png" alt="PCA9685 servo driver board with six MG90S servos connected on channels 0 to 5" width="800" height="307"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;PCA9685 has 16 channels - connect 6 MG90S servos to channels 0 to 5.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ry2jqtun7u64ep7r9d4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ry2jqtun7u64ep7r9d4.png" alt="Wiring diagram showing Arduino Nano connected to PCA9685 via I2C, joystick to A0 and A1, and external 5V powering PCA9685 V+" width="211" height="213"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Arduino + PCA9685 on I2C, joystick on A0/A1, external 5V to PCA9685's V+.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All modules are connected, and the PCA9685 has a dedicated 5V supply for servo power with a shared ground.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Install Library + Test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Verify the PCA9685 is communicating and can center the servos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Install the &lt;strong&gt;Adafruit PWM Servo Driver&lt;/strong&gt; library in the Arduino IDE. Upload the following sketch to initialize the driver and set channels 0 to 5 to their midpoint pulse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_PWMServoDriver.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Adafruit_PWMServoDriver&lt;/span&gt; &lt;span class="n"&gt;pwm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Adafruit_PWMServoDriver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;SERVO_MIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVO_MAX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;pwm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;pwm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setPWMFreq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;pwm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setPWM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SERVO_MIN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SERVO_MAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All connected servos move to a centered position and hold steady.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Joystick Control Sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Control the base and shoulder joints using the joystick analog axes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Wire the joystick outputs to A0 and A1 (as shown in the wiring diagram). Upload this loop code that reads analog values, applies a deadband, updates target angles, and writes PWM values to the PCA9685 on channels 0 and 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;curBase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curShoulder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;xRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;yRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;analogRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xRaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;curBase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;constrain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curBase&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;xRaw&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yRaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;curShoulder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;constrain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curShoulder&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;yRaw&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;pwm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setPWM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVO_MIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVO_MAX&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;pwm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setPWM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curShoulder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVO_MIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SERVO_MAX&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Moving the joystick smoothly rotates the base and moves the shoulder, without jitter when the joystick is near center.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Use It
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Operate the arm with joystick control and confirm the display and joint mapping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Use joystick X for base rotation and joystick Y for shoulder movement. Use the additional buttons to cycle through elbow, wrist, and gripper control. Watch the OLED to confirm the current joint angle values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4oagsp98cbob2e1zpomx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4oagsp98cbob2e1zpomx.png" alt="Robotic arm being controlled with a KY-023 joystick module, mapping X to base rotation and Y to shoulder movement" width="800" height="369"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Joystick X -&amp;gt; base rotation, Y -&amp;gt; shoulder. Two more buttons cycle through elbow / wrist / gripper.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fov01orhxby1tn2b231lj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fov01orhxby1tn2b231lj.jpg" alt="Arduino Nano robotic arm operating with multiple servos moving smoothly while an SSD1306 OLED shows the current joint angle" width="480" height="360"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Smooth servo motion; OLED shows current angle on each joint.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The arm responds smoothly to joystick input, and the OLED updates with the joint angles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Where to Take It Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify practical next upgrades once basic motion control is working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Consider these extensions after your core wiring and control loop are stable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a Bluetooth/Wi-Fi link - phone-app control (HM-10 or ESP32)&lt;/li&gt;
&lt;li&gt;Record/playback joint positions for repeatable demos (animatronics)&lt;/li&gt;
&lt;li&gt;Add inverse-kinematics math - control by target (x, y, z) instead of joint angles&lt;/li&gt;
&lt;li&gt;Pair with a webcam + computer vision - pick objects by color or shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a clear roadmap for expanding the project beyond manual joystick control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This build uses an Arduino Nano, a PCA9685 servo driver, and six MG90S servos to create a 6-DOF robotic arm you can drive with a joystick while viewing joint angles on an SSD1306 OLED.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>robotics</category>
      <category>electronics</category>
      <category>diy</category>
    </item>
    <item>
      <title>Arduino Nano MPU6050 + NEO-6M: GPS + IMU Data Logger</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:17:57 +0000</pubDate>
      <link>https://dev.to/shilleh/arduino-nano-mpu6050-neo-6m-gps-imu-data-logger-2id</link>
      <guid>https://dev.to/shilleh/arduino-nano-mpu6050-neo-6m-gps-imu-data-logger-2id</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Arduino Nano V3.0 vehicle data logger:&lt;/strong&gt; Use an MPU6050 IMU for G-force (acceleration and braking) plus a GT-U7 NEO-6M GPS for position and speed, then write everything to a microSD card as a CSV you can analyze later.&lt;/p&gt;

&lt;p&gt;The logger records a new row on each GPS update (about 1 Hz). After a drive, you can plot speed and G-forces over your route for track-day telemetry or general driving analysis.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~2 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A self-contained data logger that records GPS + IMU data to microSD for offline analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2r1twr5sqcp3k1pqx52x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2r1twr5sqcp3k1pqx52x.jpg" alt="Arduino Nano vehicle data logger with MPU6050 IMU, NEO-6M GPS, and microSD adapter assembled" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MPU6050 + NEO-6M + microSD = track-day data logger, no subscription.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/mpu-6050-pre-soldered-6-dof-accelerometer" rel="noopener noreferrer"&gt;MPU6050 IMU&lt;/a&gt; - measures acceleration and gyro data for G-force logging.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/gt-u7-gps-module-presoldered-1" rel="noopener noreferrer"&gt;GT-U7 NEO-6M GPS&lt;/a&gt; - provides latitude/longitude and speed fixes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/micro-sd-tf-card-adapter-reader-module-spi-interface-for-arduino" rel="noopener noreferrer"&gt;Micro SD Card Adapter&lt;/a&gt; - writes the CSV log to a microSD card over SPI.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/arduino-nano-v3-presoldered-ch340g-atmega328p" rel="noopener noreferrer"&gt;Arduino Nano V3.0&lt;/a&gt; - reads sensors and stores the data.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;Dupont Jumper Wires&lt;/a&gt; - makes the breadboard and module connections.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;microSD card (≤32 GB FAT32)&lt;/li&gt;
&lt;li&gt;12V to 5V buck (or USB-C car charger) for power&lt;/li&gt;
&lt;li&gt;Project enclosure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: The microSD card should be formatted as FAT32. The GPS module is connected using SoftwareSerial, the MPU6050 uses I2C, and the SD adapter uses SPI.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - Wire It Up
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the Arduino Nano to the MPU6050 (I2C), the NEO-6M GPS (SoftwareSerial), and the microSD adapter (SPI).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Build the circuit on a breadboard and double-check power and signal wiring before plugging into your vehicle power source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqdaa8vu3c9c96i2ndfl9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqdaa8vu3c9c96i2ndfl9.jpg" alt="Arduino Nano wired on a breadboard with MPU6050 IMU, NEO-6M GPS module, and SPI microSD adapter" width="800" height="592"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Arduino + MPU6050 + GPS + SD adapter on a small breadboard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qj1udmwk6dkuq4dmucj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qj1udmwk6dkuq4dmucj.jpg" alt="Wiring diagram showing Arduino Nano connected to MPU6050 over I2C, NEO-6M GPS over SoftwareSerial, and microSD adapter over SPI" width="444" height="648"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;GPS on SoftwareSerial; MPU6050 on I2C; SD adapter on SPI.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All modules are powered and connected, ready for uploading the sketch.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Upload the Sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Read GPS and IMU data, then log it to a CSV file on the microSD card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Install the required libraries, compile, and upload the sketch to the Arduino Nano. The logger opens &lt;code&gt;trip.csv&lt;/code&gt; and appends a header row on startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_MPU6050.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;TinyGPS++.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SoftwareSerial.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SD.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Adafruit_MPU6050&lt;/span&gt; &lt;span class="n"&gt;mpu&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;TinyGPSPlus&lt;/span&gt; &lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;SoftwareSerial&lt;/span&gt; &lt;span class="nf"&gt;gpsSer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;gpsSer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;mpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trip.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FILE_WRITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"time,lat,lng,speed_kmh,ax,ay,az,gz"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gpsSer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gpsSer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isUpdated&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sensors_event_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;mpu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%lu,%.6f,%.6f,%.1f,%.2f,%.2f,%.2f,%.2f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;gps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kmph&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acceleration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acceleration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acceleration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gyro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; When the GPS gets fixes, the Arduino writes rows to &lt;code&gt;trip.csv&lt;/code&gt; on the microSD card.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Build the Enclosure
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Secure the electronics so they can run reliably in a vehicle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Mount the breadboard/modules inside an enclosure and route the power cable so the logger can be powered from a car USB port or a regulated 5V supply.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42rf97qlvp1ghvhn75e8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F42rf97qlvp1ghvhn75e8.jpg" alt="Arduino Nano vehicle data logger mounted inside a small enclosure with power cable routed for in-car use" width="552" height="339"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;All in a small box; ribbon cable carries USB power from a car USB port.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A compact, protected logger that can be placed in the vehicle for driving tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Drive and Log
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Record a trip so you have real GPS and IMU data to analyze.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Plug the logger into power in the car and drive. The sketch logs a CSV row each time the GPS reports an updated location (about 1 Hz).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fozynekisr843fqkvkap6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fozynekisr843fqkvkap6.jpg" alt="Arduino Nano data logger powered from a car USB port while writing GPS and IMU data to microSD" width="365" height="333"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Plug into car USB; logger writes CSV row every GPS fix (~1Hz).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; After your drive, the microSD card contains &lt;code&gt;trip.csv&lt;/code&gt; with time, location, speed, and acceleration/gyro readings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Analyze the CSV
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Turn the logged data into something you can visualize and learn from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Remove the microSD card and open the CSV in Python, Excel, or import it into mapping tools (for example, Google Earth workflows) to plot the route and overlay speed or G-force data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvkn9m4ziqdlia4uhqlsy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvkn9m4ziqdlia4uhqlsy.jpg" alt="Map visualization created from the Arduino Nano GPS and IMU CSV log showing route and telemetry overlay" width="800" height="492"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Open the CSV in Python/Excel/Google Earth; plot the route with speed/G-force heatmaps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A plotted route and telemetry that helps you review braking, cornering, and speed patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Where to Take It Next
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify optional upgrades you can add after the basic logger works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Consider expanding the build with extra sensors or connectivity.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a BME280 for cabin temperature/humidity logs&lt;/li&gt;
&lt;li&gt;Stream live data to a phone over HM-10 BLE for in-car display&lt;/li&gt;
&lt;li&gt;OBD-II integration via MCP2515 to log RPM, throttle, and coolant temp&lt;/li&gt;
&lt;li&gt;ESP32 + Wi-Fi sync to auto-upload trip logs when you get home&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A clear roadmap for upgrades without changing the core data-logging workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With an Arduino Nano V3.0, an MPU6050, a NEO-6M GPS, and a microSD adapter, you can build a vehicle data logger that records GPS position, speed, and G-forces into a CSV for offline analysis. Commercial track-day loggers do the same job, but this approach keeps costs low and gives you full ownership of your data.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building automotive telematics firmware for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>gps</category>
      <category>electronics</category>
      <category>diy</category>
    </item>
    <item>
      <title>ESP32 HX711: Beehive Weight and Climate Dashboard</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:15:29 +0000</pubDate>
      <link>https://dev.to/shilleh/esp32-hx711-beehive-weight-and-climate-dashboard-1e1i</link>
      <guid>https://dev.to/shilleh/esp32-hx711-beehive-weight-and-climate-dashboard-1e1i</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ESP32 + HX711 + DS18B20 + DHT22 beehive monitor:&lt;/strong&gt; Build a Wi-Fi connected beehive monitor that tracks hive weight (honey yield), internal temperature, and humidity, then streams the readings to a phone-accessible dashboard.&lt;/p&gt;

&lt;p&gt;The HX711 with a load cell measures hive weight in grams. The DS18B20 waterproof probe reads internal temperature. The DHT22 measures ambient temperature and humidity. The ESP32 sends everything over Wi-Fi for remote viewing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~3 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate / Advanced&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A solar-friendly beehive monitor logging weight, temperature, and humidity to a dashboard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7o7u04kzpxi1d56jskqu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7o7u04kzpxi1d56jskqu.jpg" alt="ESP32 beehive monitor installed on a hive cover with HX711 load cell, DS18B20 probe, and DHT22 sensor" width="602" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;HX711 + load cell + DS18B20 + DHT22 + ESP32 for remote beekeeping data.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/hx711-weighing-pressure-sensor-module-large-presoldered" rel="noopener noreferrer"&gt;HX711 Load Cell Amplifier&lt;/a&gt; - reads the strain gauge bridge and outputs stable weight readings to the ESP32.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/ds18b20-waterproof-digital-temp-sensor-probe-1m-for-arduino-pi" rel="noopener noreferrer"&gt;DS18B20 Waterproof Probe&lt;/a&gt; - measures internal hive temperature.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-dht22-with-cables" rel="noopener noreferrer"&gt;DHT22 Temperature &amp;amp; Humidity Sensor&lt;/a&gt; - measures ambient temperature and relative humidity.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/esp32-wroom-dev-board-cp2102-usb-c-presoldered" rel="noopener noreferrer"&gt;ESP32-WROOM Dev Board&lt;/a&gt; - runs the firmware and provides Wi-Fi connectivity.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/18650-tp4056-1a-3-7-4-2v-lipo-battery-charging-board-micro-usb-with-current-protection" rel="noopener noreferrer"&gt;TP4056 LiPo Charger&lt;/a&gt; - charges and protects a single-cell LiPo/18650 battery for off-grid power.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;120 PCS Dupont Jumper Wires&lt;/a&gt; - makes prototyping and sensor wiring easier.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;50-100 kg strain-gauge load cell (4 half-bridge cells in a Wheatstone configuration) - supports the hive and provides the strain signal for the HX711.&lt;/li&gt;
&lt;li&gt;2 plywood platforms (one above, one below the cells) - creates a rigid sandwich so only vertical load reaches the cells.&lt;/li&gt;
&lt;li&gt;Optional: 6V solar panel for permanent off-grid power.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: Load cells need to be mechanically isolated so only the hive's weight passes through them. Use rubber feet between everything.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - Set up the hive
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Start with a stable hive placement before adding the scale and sensors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Choose a level location in the apiary and confirm the hive sits solidly without rocking. You will add a scale platform underneath in the next step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1m0uc7gkplzdzfn8tofs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1m0uc7gkplzdzfn8tofs.jpg" alt="Langstroth beehive in an apiary before installing ESP32 sensors and load cell platform" width="800" height="564"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Standard Langstroth hive to instrument.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A stable hive location ready for the scale platform.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Build the scale platform
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Create a mechanical platform that routes the hive weight through the load cells.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Mount four half-bridge load cells at the corners between two plywood platforms so the hive sits on the top platform and the bottom platform contacts the ground. Ensure the assembly is rigid and mechanically isolated.&lt;/p&gt;

&lt;p&gt;Wire the 4 half-bridge cells into a single full-bridge Wheatstone configuration so you can read them with one HX711 module. Many beekeeper kits ship pre-wired.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkfciie3kfive6pei04qx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkfciie3kfive6pei04qx.jpg" alt="Plywood scale platform under a beehive using four corner-mounted load cells wired for an HX711" width="800" height="430"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Four load cells at the corners with a plywood sandwich above and below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A complete platform that can measure hive weight changes reliably.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - Wire the electronics
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the HX711, DS18B20, and DHT22 to the ESP32 GPIO pins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Assemble the ESP32 and sensors on your mounting board/enclosure. Wire the sensors as shown in the wiring diagram: HX711 to GPIO 16 and 17, DS18B20 to GPIO 4, and DHT22 to GPIO 5.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmw9gl5wzx0muhr4g25tz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmw9gl5wzx0muhr4g25tz.jpg" alt="ESP32 development board connected to HX711 module, DS18B20 waterproof probe, and DHT22 sensor on a mounting board" width="720" height="394"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ESP32 with three sensors: HX711, DS18B20, and DHT22.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabrrqiiu1rihiapfmn11.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabrrqiiu1rihiapfmn11.jpg" alt="Wiring diagram showing ESP32 connected to HX711 on GPIO 16 and 17, DS18B20 on GPIO 4, and DHT22 on GPIO 5" width="749" height="795"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;HX711 to GPIO 16/17, DS18B20 to GPIO 4, DHT22 to GPIO 5.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All sensors are wired to the ESP32 and ready for firmware.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4 - Upload the sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Read weight, internal temperature, ambient temperature, and humidity, then print the values to Serial (and optionally push to a server).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Compile and upload the following Arduino sketch to the ESP32. Update the Wi-Fi SSID and password, and calibrate the &lt;code&gt;CAL&lt;/code&gt; value for your specific load cell setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WiFi.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;HX711.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;OneWire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;DallasTemperature.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;DHT.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;span class="n"&gt;HX711&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;OneWire&lt;/span&gt; &lt;span class="nf"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;DallasTemperature&lt;/span&gt; &lt;span class="nf"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;DHT&lt;/span&gt; &lt;span class="nf"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DHT22&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;CAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;420.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// calibrate this for your load cell&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SSID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"PASS"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tare&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_units&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestTemperatures&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;internalT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getTempCByIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;ambientT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readTemperature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dht&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;readHumidity&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kg=%.2f intT=%.1fC ambT=%.1fC RH=%.0f%%&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;internalT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ambientT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...push to ThingSpeak / your own server every 5 minutes&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Serial output shows weight plus internal and ambient temperature and humidity readings at the configured interval.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Deploy at the apiary
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Install the monitor so it runs continuously in real conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Mount the electronics securely, protect the sensors and wiring from weather, and place the hive on the scale platform. If using solar power, install the panel and verify the charging setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujsaeb9vjjc989h8d2n5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujsaeb9vjjc989h8d2n5.jpg" alt="Solar-powered ESP32 beehive monitor deployed in an apiary reporting sensor readings every five minutes" width="799" height="509"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Solar-powered deployment reporting every 5 minutes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F000xsxa1zqcrscgq845e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F000xsxa1zqcrscgq845e.jpg" alt="Mobile phone dashboard displaying beehive weight and temperature and humidity readings from an ESP32" width="799" height="346"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Live weight, internal and ambient temperature, and humidity from anywhere.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The system runs in place and you can view readings in your dashboard or logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Extend the project
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Identify safe next upgrades without changing the core build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Consider expanding with one of the following additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add LoRa (SX1262) for off-grid apiaries beyond Wi-Fi range&lt;/li&gt;
&lt;li&gt;Detect swarm events from sudden weight drops plus a spike in audio (microphone)&lt;/li&gt;
&lt;li&gt;Log to ThingSpeak / InfluxDB for multi-year datasets&lt;/li&gt;
&lt;li&gt;Alert on temperature anomalies (overheating risk for the colony)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A clear roadmap for future enhancements once the baseline monitor is stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built an ESP32-based beehive monitor using an HX711 load cell amplifier for weight tracking, plus a DS18B20 probe and DHT22 sensor for temperature and humidity readings. With the data streaming over Wi-Fi, you can watch hive health and honey flow trends without opening the hive as often.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>iot</category>
      <category>diy</category>
      <category>electronics</category>
    </item>
    <item>
      <title>ESP32 DS18B20: Wi-Fi Smoker Thermometer Alerts</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:12:50 +0000</pubDate>
      <link>https://dev.to/shilleh/esp32-ds18b20-wi-fi-smoker-thermometer-alerts-1l3i</link>
      <guid>https://dev.to/shilleh/esp32-ds18b20-wi-fi-smoker-thermometer-alerts-1l3i</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ESP32 + DS18B20 Wi-Fi BBQ / smoker thermometer:&lt;/strong&gt; A waterproof DS18B20 probe measures meat or smoker temperature while an ESP32 shows it on a small OLED and sends a Telegram alert when your target temperature is reached.&lt;/p&gt;

&lt;p&gt;This build is designed for long cooks so you can step away and still get notified the moment the food is ready.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; ~1.5 hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A Wi-Fi-connected meat thermometer with OLED display and Telegram alerts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqj7lrcmftj3m0hj01w4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqj7lrcmftj3m0hj01w4.jpg" alt="ESP32 BBQ thermometer with DS18B20 probe and OLED display used for smoker temperature monitoring" width="800" height="667"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;DS18B20 waterproof probe + ESP32 + OLED + Telegram alerts.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;
&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/ds18b20-waterproof-digital-temp-sensor-probe-1m-for-arduino-pi" rel="noopener noreferrer"&gt;DS18B20 Waterproof Probe (1m)&lt;/a&gt; - food-safe stainless probe for meat or smoker chamber temperature.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/esp32-wroom-dev-board-cp2102-usb-c-presoldered" rel="noopener noreferrer"&gt;ESP32-WROOM Dev Board&lt;/a&gt; - reads the sensor, drives the display, and connects to Wi-Fi.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/0-96-i2c-blue-oled-display-module-4-pin-ssd1306" rel="noopener noreferrer"&gt;0.96" I²C SSD1306 OLED&lt;/a&gt; - shows live temperature and status on the device.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/ky-006-passive-piezo-buzzer-alarm-module-for-arduino-projects" rel="noopener noreferrer"&gt;KY-006 Buzzer (audible alarm)&lt;/a&gt; - local audible alert when target temperature is reached.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/18650-tp4056-1a-3-7-4-2v-lipo-battery-charging-board-micro-usb-with-current-protection" rel="noopener noreferrer"&gt;TP4056 LiPo Charger&lt;/a&gt; - optional charging module for portable power.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;120 PCS Dupont Jumper Wires&lt;/a&gt; - wiring for the probe, OLED, and buzzer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;4.7kΩ resistor - required 1-Wire pull-up resistor for the DS18B20 data line&lt;/li&gt;
&lt;li&gt;18650 cell - optional battery for portability&lt;/li&gt;
&lt;li&gt;Telegram bot token + chat ID - required for Telegram notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: The DS18B20 probe is rated 0 to 125°C, which is suitable for meat and smoker chamber temperatures. The stainless steel sheath is food-safe, but wipe it with food-safe sanitizer before each use.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1 - Inspect the probe
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Confirm you have a waterproof DS18B20 probe and understand its role in the build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Check the cable jacket and stainless tip for damage. Identify the three probe wires used for power, ground, and data (your probe colors may vary).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzun83bslbxh5mjs32lc6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzun83bslbxh5mjs32lc6.jpg" alt="DS18B20 waterproof temperature probe used with an ESP32 for BBQ and smoker temperature sensing" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;1m stainless steel waterproof DS18B20 probe.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You are ready to wire the DS18B20 to the ESP32 using a 1-Wire pull-up resistor.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Wire the ESP32, DS18B20, OLED, and buzzer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Connect the temperature probe, OLED display (I2C), and buzzer to the ESP32.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Make the connections below. Add the 4.7kΩ pull-up resistor from the DS18B20 data line to 3.3V.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui0f3km3unxph9l263pq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fui0f3km3unxph9l263pq.png" alt="Wiring diagram showing ESP32 connected to a DS18B20 temperature probe on GPIO 4 with 4.7k pull-up, SSD1306 OLED on I2C GPIO 21 and 22, and buzzer on GPIO 13" width="799" height="458"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Probe data to GPIO 4 (with 4.7kΩ pull-up). OLED on I2C. Buzzer on GPIO 13.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Probe red to 3.3V, black to GND, yellow to GPIO 4 (with 4.7kΩ to 3.3V)&lt;/li&gt;
&lt;li&gt;OLED VCC to 3.3V, GND to GND, SDA to GPIO 21, SCL to GPIO 22&lt;/li&gt;
&lt;li&gt;Buzzer signal to GPIO 13, GND to GND&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All modules are powered from 3.3V and share a common ground, and the DS18B20 data line has a pull-up resistor.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - Upload the sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Program the ESP32 to read the DS18B20, display the temperature on the OLED, and send a Telegram alert at the target temperature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Install the required libraries in your Arduino IDE, then paste and upload the sketch. Replace &lt;code&gt;SSID&lt;/code&gt;, &lt;code&gt;PASS&lt;/code&gt;, &lt;code&gt;TOKEN&lt;/code&gt;, and &lt;code&gt;CHAT&lt;/code&gt; with your Wi-Fi and Telegram details.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4kbqs2j6i6w2rluwzqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn4kbqs2j6i6w2rluwzqz.png" alt="ESP32 controller board used for a DS18B20 BBQ thermometer build, ready to be placed in a small enclosure" width="800" height="465"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Compact ESP32-based controller suitable for a small project box.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WiFi.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WiFiClientSecure.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;UniversalTelegramBot.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;OneWire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;DallasTemperature.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_GFX.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Adafruit_SSD1306.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;SSID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"WIFI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"PWD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"BOT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CHAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"CHAT"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;95.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// target meat temp (e.g. brisket)&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;BUZZ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;OneWire&lt;/span&gt; &lt;span class="nf"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;DallasTemperature&lt;/span&gt; &lt;span class="nf"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;bus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Adafruit_SSD1306&lt;/span&gt; &lt;span class="nf"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;WiFiClientSecure&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;UniversalTelegramBot&lt;/span&gt; &lt;span class="nf"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;alerted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSD1306_SWITCHCAPVCC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x3C&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUZZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PASS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;WL_CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCACert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TELEGRAM_CERTIFICATE_ROOT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestTemperatures&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getTempCByIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clearDisplay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setTextSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setTextColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SSD1306_WHITE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setTextSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"READY"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;oled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;TARGET&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;alerted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHAT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"🥩 Meat is ready! "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUZZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUZZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;alerted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The OLED shows the current temperature, and when the reading reaches &lt;code&gt;TARGET&lt;/code&gt;, the buzzer sounds and a Telegram message is sent once.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Build the enclosure
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Package the electronics so they can sit outside the smoker while the probe stays inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Place the ESP32, OLED, and buzzer in a heat-resistant enclosure. Route the probe cable through a grommet or strain relief so the cable is protected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8s25fiim0mkogzjmdv1t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8s25fiim0mkogzjmdv1t.jpg" alt="ESP32 BBQ thermometer electronics mounted in a heat-resistant enclosure with DS18B20 probe cable routed through a grommet" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Heat-resistant enclosure with probe cable routed through a grommet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A portable controller you can place safely away from heat and moisture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Use it during a cook
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Monitor meat temperature in real time and receive an alert when it is ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Insert the DS18B20 probe into the meat (or position it where you want to measure chamber temperature). Keep the ESP32 enclosure outside the smoker. Power the device and connect it to your Wi-Fi network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ywbeb1bua4vvssxdzcl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ywbeb1bua4vvssxdzcl.jpg" alt="OLED display showing live temperature reading from a DS18B20 probe while the ESP32 sits outside a smoker" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Live temperature on the OLED, with Telegram alerts when target is reached.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You can read temperature on the OLED, and you receive a Telegram notification when the temperature crosses the target value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Optional next improvements
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Extend the project after the basic build is working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Consider adding features such as a second probe, data logging, or active temperature control.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a second probe to track meat and smoker chamber temperature&lt;/li&gt;
&lt;li&gt;Log data over time for low-and-slow cook curves&lt;/li&gt;
&lt;li&gt;Add a relay-controlled fan for active smoker temperature control&lt;/li&gt;
&lt;li&gt;Pair with a meat-doneness lookup table (brisket 93°C, pork shoulder 88°C, chicken 74°C)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; A clear path to expand the project while keeping the current build as the foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built an ESP32 and DS18B20 Wi-Fi BBQ / smoker thermometer that displays live temperature on an SSD1306 OLED and sends a Telegram alert at your target temperature. This makes long cooks easier because you can monitor the temperature without standing by the smoker.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;. Reference inspiration: &lt;a href="https://www.instructables.com/ESP32-NTP-Temperature-Probe-Cooking-Thermometer-Wi/" rel="noopener noreferrer"&gt;Instructables&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>iot</category>
      <category>diy</category>
      <category>electronics</category>
    </item>
    <item>
      <title>TP4056 18650: Build a USB-C Power Bank</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:09:52 +0000</pubDate>
      <link>https://dev.to/shilleh/tp4056-18650-build-a-usb-c-power-bank-4ka4</link>
      <guid>https://dev.to/shilleh/tp4056-18650-build-a-usb-c-power-bank-4ka4</guid>
      <description>&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TP4056 USB-C charging board + 18650 cells:&lt;/strong&gt; Build a repairable 9000 mAh USB-C power bank that charges from a USB-C charger and outputs 5 V over USB-A using a boost converter.&lt;/p&gt;

&lt;p&gt;Commercial USB-C power banks cost $30+ for about 10,000 mAh. With three 18650 cells you can reach roughly the same capacity for under $15, and you can replace individual cells when they wear out. The basic setup is a TP4056 USB-C charger up front, three 18650s in parallel, and a small DC-DC boost converter on the back end to deliver 5 V USB output.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; 45 to 90 minutes (plus charge and rest time for cell matching)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Beginner to Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A 3-cell (3P) 18650 power bank charged by TP4056 and boosted to 5 V USB output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Forpb3hwnshhcgy3ep9ci.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Forpb3hwnshhcgy3ep9ci.jpg" alt="DIY 18650 USB-C power bank enclosure with 18650 cells and USB output" width="440" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/tp4056-1a-lipo-battery-charging-board-type-c-with-current-protection" rel="noopener noreferrer"&gt;TP4056 USB-C Charging Board (with protection)&lt;/a&gt; - charges the 1S (3.7 V nominal) 18650 pack and provides protected output pads&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/1s-3-7v-3a-2mos-bms-li-ion-18650-battery-protection-board" rel="noopener noreferrer"&gt;1S 3A BMS Protection Board&lt;/a&gt; - use this if your TP4056 board does not include protection&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/4-x-18650-battery-holder-box-with-wire-no-cover" rel="noopener noreferrer"&gt;4 x 18650 Battery Holder&lt;/a&gt; - holds the cells; populate 3 slots for a 3P pack&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;DuPont Wires&lt;/a&gt; - wiring and quick connections during testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Three matched 18650 cells (3000 mAh each for an approximate 9000 mAh parallel pack)&lt;/li&gt;
&lt;li&gt;A small DC-DC boost converter (5 V, 2 A output), for example MT3608, or a dedicated USB-out booster module&lt;/li&gt;
&lt;li&gt;3D-printed or off-the-shelf project enclosure&lt;/li&gt;
&lt;li&gt;USB-A panel-mount jack for the output (or use the booster module's onboard USB-A)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: For safety, use a TP4056 module with proper protection (or add a 1S BMS). Do not parallel cells with significantly different voltages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 - Plan the architecture (charger + parallel pack + boost)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Understand the correct block diagram before wiring anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Build a single-cell-voltage pack (3.7 V nominal) by putting your 18650 cells in parallel, not series. Three parallel 3000 mAh cells behave like one large 9000 mAh cell at the same voltage. A single TP4056 charges the bank as if it were one cell, and a single 5 V boost converter provides the USB output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9ckm7wes5cme9c4u6yk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi9ckm7wes5cme9c4u6yk.png" alt="Wiring diagram for TP4056 charging a 3P 18650 pack feeding a 5V boost converter and USB-A output" width="799" height="509"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Overall signal and power flow for the power bank.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;USB-C IN -&amp;gt; TP4056 -&amp;gt; B+/B- -&amp;gt; 3x 18650 &lt;span class="o"&gt;(&lt;/span&gt;parallel&lt;span class="o"&gt;)&lt;/span&gt;
                            -&amp;gt; Boost Converter VIN
                                           VOUT -&amp;gt; USB-A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You know where the battery connects (B+/B-) and where the load connects (OUT+/OUT- into the boost converter).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - Match the 18650 cells before paralleling
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Prevent large equalization currents when connecting cells in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Match the cells' resting voltage to within about 50 mV before paralleling. Charge all three to 4.20 V individually first, let them rest for 24 hours, then measure again. They should be within about 10 mV.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; All three cells are at nearly the same voltage before you wire them together in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Wire the battery holder for a 3P pack
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Create one high-capacity 1S pack with two output wires.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Use the 4-cell holder but populate only 3 positions. Orient all three cells the same way (parallel means all positives on one end, all negatives on the other). Solder a thick wire across all three positive tabs and a thick wire across all three negative tabs. This forms a 9000 mAh equivalent "cell" with two leads.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb9995n3u3wb5qod08fe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwb9995n3u3wb5qod08fe.jpg" alt="Three 18650 cells installed in a holder and wired in parallel with common positive and common negative connections" width="800" height="370"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;All cells face the same direction to form a parallel pack.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a single pair of battery leads representing the combined parallel pack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Connect the TP4056 and (optional) BMS correctly
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Wire charging and protected output safely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; If your TP4056 USB-C board has built-in protection (often indicated by a DW01 protection chip on the bottom), it typically exposes four pads: B+/B- to the battery, and OUT+/OUT- to the load. The default charge current is usually 1 A. If your TP4056 board lacks protection, add a 1S BMS protection board as appropriate for your module and wiring plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The battery is connected to B+/B-, and you have a protected output (OUT+/OUT-) ready to feed the boost converter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Add and adjust the 5V boost converter
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Convert the battery voltage (about 3.0 to 4.2 V) into a stable 5 V USB output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Connect the boost converter input to the TP4056 output pads (OUT+/OUT-). If using an adjustable module like an MT3608, set the output with a multimeter to about 5.05 V before connecting a phone or other device. Alternatively, use a dedicated "5V USB power-bank booster" module that already includes a USB-A jack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd3j6bl29h5rpbc8s3fj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd3j6bl29h5rpbc8s3fj.jpg" alt="MT3608 boost converter wired to a battery pack with multimeter-adjusted 5V output" width="800" height="648"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Adjust the trim pot to set a safe 5 V output.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You can measure a steady ~5 V at the boost converter output (or at the USB-A output jack).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Estimate capacity, runtime, and charge time
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Set realistic expectations for usable 5 V output and recharge time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Use the following reference calculations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pack capacity:&lt;/strong&gt; 3 x 3000 mAh @ 3.7 V = 11.1 Wh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usable at 5 V output (boost is ~85% efficient):&lt;/strong&gt; ~9.4 Wh / 5 V = 1880 mAh of "USB 5V" output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iPhone 15 (battery ~12 Wh):&lt;/strong&gt; roughly 0.8 full charges per power-bank fill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Charge time from a 1 A USB-C charger:&lt;/strong&gt; ~12 hours for a full empty-to-full bank&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You understand why a "9000 mAh" 3.7 V pack delivers significantly less mAh at 5 V after conversion losses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 - (Optional) Add battery-level LED indication
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Add a simple battery level indicator similar to commercial power banks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Add a 4-LED voltage meter solution (for example, an LM3914-based indicator or a small ATtiny reading cell voltage) to create a four-bar indicator. This is a low-cost upgrade that improves usability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You have a basic LED indicator that reflects battery voltage level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built a DIY 18650 power bank using a TP4056 USB-C charging board, a 3P parallel cell pack, and a 5 V boost converter for USB output. This project covers lithium charging basics, protection, boost conversion, and pack assembly in a design where you can replace individual cells instead of discarding the whole bank.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Inspiration source: &lt;a href="https://www.instructables.com/Homemade-DIY-Power-Bank-Using-18650-Battery/" rel="noopener noreferrer"&gt;"Homemade DIY Power Bank Using 18650 Battery" on Instructables&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>electronics</category>
      <category>diy</category>
      <category>arduino</category>
      <category>iot</category>
    </item>
    <item>
      <title>ESP32 LVGL: Touchscreen Smart Switch UI</title>
      <dc:creator>Shilleh</dc:creator>
      <pubDate>Mon, 29 Jun 2026 02:07:29 +0000</pubDate>
      <link>https://dev.to/shilleh/esp32-lvgl-touchscreen-smart-switch-ui-36k1</link>
      <guid>https://dev.to/shilleh/esp32-lvgl-touchscreen-smart-switch-ui-36k1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrvouj3cm5baic03203i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffrvouj3cm5baic03203i.jpg" alt="ESP32 LVGL 2.8-inch resistive touchscreen smart display showing a smart switch-style UI" width="799" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ESP32 + LVGL touchscreen display smart switch UI:&lt;/strong&gt; Build a slick touchscreen interface on an ESP32 LVGL 2.8-inch Smart Display that can toggle a relay, show live temperature data, and publish state to Home Assistant over MQTT.&lt;/p&gt;

&lt;p&gt;Using the LVGL graphics library gets you modern UI widgets (buttons, sliders, gauges, charts) and smooth partial redraws that feel closer to commercial panels. The trade-off is higher RAM usage, but the ESP32 has enough SRAM for typical LVGL projects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; About 30 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill level:&lt;/strong&gt; Intermediate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you will build:&lt;/strong&gt; A working ESP32 touchscreen smart switch UI with relay control, sensor display, and MQTT publishing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Parts List
&lt;/h2&gt;

&lt;h3&gt;
  
  
  From ShillehTek
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/esp32-lvgl-2-8-inch-240x320-smart-display-with-resistive-touch-wifi-bluetooth-dev-board" rel="noopener noreferrer"&gt;ESP32 LVGL 2.8" Smart Display (Resistive Touch)&lt;/a&gt; - ESP32 + TFT + touch controller on one board for LVGL UI builds.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/1-channel-5v-relay-module" rel="noopener noreferrer"&gt;1-Channel 5V Relay Module&lt;/a&gt; - for switching a load from the touchscreen (or use the &lt;a href="https://shillehtek.com/products/4-channel-5v-relay-module" rel="noopener noreferrer"&gt;4-channel version&lt;/a&gt; for multiple loads).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-dht22-with-cables" rel="noopener noreferrer"&gt;DHT22 Sensor&lt;/a&gt; - provides the temperature reading for the on-screen gauge.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shillehtek.com/products/shillehtek-120pcs-multicolored-dupont-wire" rel="noopener noreferrer"&gt;DuPont Wires&lt;/a&gt; - for quick sensor and relay connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  External
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;USB-C cable - to power and program the ESP32 display board.&lt;/li&gt;
&lt;li&gt;An MQTT broker - Mosquitto on a Raspberry Pi or Home Assistant's built-in broker.&lt;/li&gt;
&lt;li&gt;Arduino IDE 2.x with the ESP32 board package and LVGL / TFT_eSPI libraries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: The ESP32 LVGL display board referenced here is a single PCB with ESP32-WROOM-32, a 240x320 ILI9341 TFT, and an XPT2046 resistive touch controller. Use the product page pinout and library configuration for your specific board revision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 - Understand why LVGL is a better UI toolkit
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Pick the right graphics approach for a touchscreen smart switch UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; If you are coming from direct-draw libraries like Adafruit_GFX (or raw TFT_eSPI drawing calls), note that those are great for simple primitives. LVGL is a retained-mode UI toolkit where widgets keep state, redraws are partial and smooth, and you can design layouts visually with SquareLine Studio and export C code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You understand the main trade-off: LVGL uses more RAM (around 30 KB minimum), but the ESP32's SRAM is typically sufficient for this class of UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqx27tgwt7zmwmbqn17e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqx27tgwt7zmwmbqn17e.jpg" alt="LVGL user interface running on an ESP32 240x320 touchscreen display" width="418" height="419"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;LVGL widgets and styling enable a more polished touchscreen UI than simple direct-draw graphics.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2 - Set up the ESP32 LVGL display hardware
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Confirm what is already integrated on the display board and what you need to wire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; The ShillehTek ESP32 LVGL display is a single PCB with the ESP32 module, ILI9341 240x320 TFT, XPT2046 resistive touch, and USB-C. Plug in USB-C to power and program the board. Use the board's product page for the correct library and pin configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Your board powers up over USB-C and you are ready to compile and upload an Arduino sketch.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - Upload a quick "Hello LVGL" sketch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Initialize LVGL and draw a basic button on the display.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Install the required libraries (LVGL and TFT_eSPI) in Arduino IDE, then upload a minimal LVGL example that initializes the display driver, sets up a draw buffer, and creates a centered button with a label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;lvgl.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;TFT_eSPI.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"touch.h"&lt;/span&gt;&lt;span class="c1"&gt;   // XPT2046 helper&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;lv_disp_draw_buf_t&lt;/span&gt; &lt;span class="n"&gt;draw_buf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;lv_color_t&lt;/span&gt; &lt;span class="n"&gt;buf1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;my_disp_flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv_disp_drv_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;lv_area_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lv_color_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;tft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startWrite&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;tft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setAddrWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;tft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pushColors&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;x2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;y1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;tft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endWrite&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;lv_disp_flush_ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;tft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;tft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;lv_disp_draw_buf_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;draw_buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;lv_disp_drv_t&lt;/span&gt; &lt;span class="n"&gt;drv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;lv_disp_drv_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;drv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;drv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush_cb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_disp_flush&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;drv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hor_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;drv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ver_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;drv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw_buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;draw_buf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;lv_disp_drv_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;drv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;lv_obj_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lv_btn_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv_scr_act&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;lbl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lv_label_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_label_set_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lbl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Hello LVGL!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;lv_timer_handler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The screen shows a centered LVGL button with the text "Hello LVGL!".&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Add a relay-toggle button to the UI
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Toggle a GPIO that controls a relay module from the touchscreen button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Choose the relay GPIO (example below uses GPIO 32). Create an LVGL button, attach an event callback, and update both the relay output level and the on-screen label when the user taps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vlk3oxxtkng8tvdx81f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6vlk3oxxtkng8tvdx81f.png" alt="LVGL touchscreen relay toggle button UI on an ESP32 smart display" width="800" height="657"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A simple LVGL button can act as a smart switch for a relay output.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;RELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;relayOn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;toggle_cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv_event_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;relayOn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;relayOn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RELAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relayOn&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lv_event_get_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_label_set_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv_obj_get_child&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                     &lt;span class="n"&gt;relayOn&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"Light ON"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Light OFF"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RELAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... LVGL init from step 3 ...&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lv_btn_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lv_scr_act&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_set_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_add_event_cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toggle_cb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LV_EVENT_CLICKED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_obj_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;lbl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lv_label_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;lv_label_set_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lbl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Light OFF"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Tapping the on-screen button flips the relay GPIO and updates the label between "Light OFF" and "Light ON".&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5 - Display live data with an animated gauge
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Show a live temperature reading on the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Read the DHT22 periodically (for example, every 2 seconds) and feed the value into an &lt;code&gt;lv_meter&lt;/code&gt; widget. This gives you a swept-needle style gauge that updates smoothly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; The screen shows a temperature gauge that updates on a timed interval as new DHT22 readings arrive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 - Publish state to Home Assistant over MQTT
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Send button/relay state changes to your MQTT broker so Home Assistant can track and control the entity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Add the PubSubClient library, connect the ESP32 to WiFi, and publish a message (example topic: &lt;code&gt;home/livingroom/light&lt;/code&gt;) whenever the LVGL button is pressed. You can also subscribe to the same topic (or a command topic) to reflect state changes initiated in Home Assistant back on the display.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpc2f6sfnhxkuc5aisml9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpc2f6sfnhxkuc5aisml9.png" alt="ESP32 LVGL display integrated with Home Assistant using MQTT topics" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;MQTT is a straightforward bridge between the touchscreen UI and Home Assistant entities.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; Home Assistant detects the MQTT messages and you can control or monitor the smart switch state from other devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 - Use SquareLine Studio for more complex UIs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; Speed up multi-screen UI creation without manually calculating layout positions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; For multi-page interfaces (settings, dashboards, animated transitions), use SquareLine Studio to design the UI visually, then export the generated C code and integrate it into your Arduino project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected result:&lt;/strong&gt; You can iterate on a polished UI faster, while still running everything locally on the ESP32.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;You built an ESP32 LVGL touchscreen smart switch UI that can toggle a relay, display live DHT22 temperature data, and publish state changes over MQTT for Home Assistant integration. This workflow gives you a modern, smooth UI on inexpensive hardware while keeping the firmware fully under your control.&lt;/p&gt;

&lt;p&gt;Want the exact parts used in this build? Grab them from &lt;a href="https://shillehtek.com" rel="noopener noreferrer"&gt;ShillehTek.com&lt;/a&gt;. If you want help customizing this project or building something for your product, check out our &lt;a href="https://shillehtek.com/pages/iot-consulting" rel="noopener noreferrer"&gt;IoT consulting services&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Attribution: This guide was inspired by &lt;a href="https://www.instructables.com/Building-an-ESP32-based-Smart-Switch-With-LVGL-Dis/" rel="noopener noreferrer"&gt;"Building an ESP32-based Smart Switch With LVGL Display" on Instructables&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>esp32</category>
      <category>iot</category>
      <category>smarthome</category>
      <category>cpp</category>
    </item>
  </channel>
</rss>
