<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ESP32 Projects Archives | Microcontroller Tutorials</title>
	<atom:link href="https://www.teachmemicro.com/category/projects/esp32-projects/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.teachmemicro.com/category/projects/esp32-projects/</link>
	<description>Microcontroller Tutorials and Resources</description>
	<lastBuildDate>Sun, 28 Jun 2026 00:43:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.2</generator>

<image>
	<url>https://www.teachmemicro.com/wp-content/uploads/2019/04/blue-icon-65x65.png</url>
	<title>ESP32 Projects Archives | Microcontroller Tutorials</title>
	<link>https://www.teachmemicro.com/category/projects/esp32-projects/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>ESP32 Time-of-Flight Sensor Tutorial (VL53L0X / VL53L1X Distance Measurement)</title>
		<link>https://www.teachmemicro.com/esp32-time-of-flight-sensor-vl53l0x/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=esp32-time-of-flight-sensor-vl53l0x</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Thu, 30 Apr 2026 21:45:36 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<category><![CDATA[Sensor Tutorial]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=11246</guid>

					<description><![CDATA[<p>When I first moved from ultrasonic sensors to Time-of-Flight (ToF) sensors, the difference was night and day. Measurements became faster, more stable, and far more precise. In this tutorial, I’ll show you exactly how I use a ToF sensor like the VL53L0X with the ESP32. What is a Time-of-Flight Sensor? A Time-of-Flight sensor measures distance &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-time-of-flight-sensor-vl53l0x/">ESP32 Time-of-Flight Sensor Tutorial (VL53L0X / VL53L1X Distance Measurement)</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>When I first moved from ultrasonic sensors to Time-of-Flight (ToF) sensors, the difference was night and day. Measurements became faster, more stable, and far more precise. In this tutorial, I’ll show you exactly how I use a ToF sensor like the VL53L0X with the ESP32.</p>
<h3><b>What is a Time-of-Flight Sensor?</b></h3>
<p>A Time-of-Flight sensor measures distance using light instead of sound. It emits an infrared laser and calculates how long it takes for the reflection to return.</p>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2026/05/tof-sensor-concept.avif"><img data-dominant-color="29282c" data-has-transparency="false" style="--dominant-color: #29282c;" decoding="async" class="aligncenter size-full wp-image-11248 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/05/tof-sensor-concept.avif" alt="VL53L0X ToF concept" width="768" height="512" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/05/tof-sensor-concept.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2026/05/tof-sensor-concept-300x200.avif 300w" sizes="(max-width: 768px) 100vw, 768px" /></a></p>
<p>Because it uses light, it avoids common ultrasonic issues like:</p>
<ul>
<li>Poor readings on soft surfaces</li>
<li>Wide beam angles</li>
<li>Slow response time</li>
</ul>
<p>With ToF sensors, I consistently get millimeter-level precision, especially indoors.</p>
<h3><b>Why Use ESP32 with ToF Sensors?</b></h3>
<p>The ESP32 is perfect for this because:</p>
<ul>
<li>Built-in I2C makes wiring simple</li>
<li>Plenty of processing power for filtering</li>
<li>Easy to expand into WiFi dashboards later</li>
</ul>
<p>This combo is something I often use for smart sensing projects.</p>
<h3><b>Materials</b></h3>
<p>For this setup, I used:</p>
<ul>
<li>ESP32 development board</li>
<li>VL53L0X (or VL53L1X) ToF sensor</li>
<li>Breadboard</li>
<li>Jumper wires</li>
</ul>
<h3><b>Wiring the ToF Sensor to ESP32</b></h3>
<p>The ToF sensor uses I2C, so wiring is straightforward.</p>
<table>
<tbody>
<tr>
<td><b>ToF Sensor</b></td>
<td><b>ESP32</b></td>
</tr>
<tr>
<td>VCC</td>
<td>3.3V</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
</tr>
<tr>
<td>SDA</td>
<td>GPIO 21</td>
</tr>
<tr>
<td>SCL</td>
<td>GPIO 22</td>
</tr>
</tbody>
</table>
<p>I always stick to 3.3V with ESP32 to avoid voltage compatibility issues.</p>
<h3><b>Installing the Library</b></h3>
<p>In Arduino IDE, install:</p>
<ul>
<li><b>Adafruit VL53L0X</b></li>
</ul>
<p>This library is reliable and easy to use, especially if you’re just starting.</p>
<h3><b>Basic ESP32 ToF Example Code</b></h3>
<p>Here’s the exact code I use for quick testing:</p>
<pre><pre><code class="language-cpp">#include &lt;Wire.h&gt;
#include &quot;Adafruit_VL53L0X.h&quot;

Adafruit_VL53L0X lox = Adafruit_VL53L0X();

void setup() {
 Serial.begin(115200);
 Wire.begin();

 if (!lox.begin()) {
   Serial.println(&quot;Failed to initialize VL53L0X&quot;);
   while (1);
 }
 Serial.println(&quot;ToF Sensor Ready!&quot;);
}

void loop() {

 VL53L0X_RangingMeasurementData_t measure;
 lox.rangingTest(&amp;measure, false);

 if (measure.RangeStatus != 4) {
   Serial.print(&quot;Distance: &quot;);
   Serial.print(measure.RangeMilliMeter);
   Serial.println(&quot; mm&quot;);
 } else {
   Serial.println(&quot;Out of range&quot;);
 }

 delay(500);
}</code></pre></pre>
<h3><b>What the Output Means</b></h3>
<p>The sensor returns distance in millimeters.</p>
<p>Typical ranges:</p>
<ul>
<li>VL53L0X → up to ~2 meters</li>
<li>VL53L1X → up to ~4 meters</li>
</ul>
<p>If you see <i>“Out of range”</i>, it usually means:</p>
<ul>
<li>Object is too far</li>
<li>Surface is not reflective enough</li>
</ul>
<h3><b>Improving Accuracy </b></h3>
<p>Raw readings can still fluctuate slightly. In real projects, I always apply simple filtering.</p>
<p>Here’s a quick averaging function:</p>
<pre><pre><code class="language-cpp">int get_average_distance(int samples) {
 int sum = 0;
 int valid = 0;

 for (int i = 0; i &lt; samples; i++) {
   VL53L0X_RangingMeasurementData_t measure;
   lox.rangingTest(&amp;measure, false);

   if (measure.RangeStatus != 4) {
     sum += measure.RangeMilliMeter;
     valid++;
   }

   delay(50);
 }

 if (valid == 0) return -1;
 return sum / valid;
}</code></pre></pre>
<p>This alone dramatically improves stability.</p>
<h3><b>Common Problems and Fixes</b></h3>
<h3><b>Sensor Not Detected</b></h3>
<p>I usually check:</p>
<ul>
<li>SDA/SCL swapped</li>
<li>Loose wiring</li>
<li>I2C address (normally 0x29)</li>
</ul>
<h3><b>Constant Invalid Readings</b></h3>
<p>This often comes from:</p>
<ul>
<li>Weak power supply</li>
<li>Too fast reading loop</li>
</ul>
<p>Adding delay usually fixes it.</p>
<h3><b>Noisy Measurements</b></h3>
<p>If readings jump around:</p>
<ul>
<li>Use averaging</li>
<li>Avoid direct sunlight</li>
<li>Keep sensor steady</li>
</ul>
<h3><b>Real-World Applications</b></h3>
<p>Once this is working, you can easily expand it into:</p>
<ul>
<li>Touchless switches</li>
<li>Distance-triggered automation</li>
<li>Liquid level sensing</li>
<li>Robot obstacle detection</li>
<li>Smart parking systems</li>
</ul>
<p>This sensor is one of those components that scales really well from beginner to advanced projects.</p>
<p>&nbsp;</p>
<h3><b>Example Project: Touchless Switch (ToF + Relay)</b></h3>
<p>One of my favorite practical uses of a ToF sensor is creating a <b>touchless switch</b>. Instead of physically pressing a button, you simply place your hand in front of the sensor to toggle a device—like a light or fan.</p>
<p>This is especially useful for:</p>
<ul>
<li>Hygiene (no physical contact)</li>
<li>Smart home projects</li>
<li>Modern UI interactions</li>
</ul>
<h3><b>How It Works</b></h3>
<p>The idea is simple:</p>
<ul>
<li>The ToF sensor continuously measures distance</li>
<li>When your hand comes within a threshold (e.g., &lt; 100 mm), it triggers an action</li>
<li>The ESP32 toggles a relay module</li>
<li>The relay acts like a physical switch (ON/OFF)</li>
</ul>
<p>I usually add a small delay or state logic to prevent repeated triggering.</p>
<h3><b>Additional Components</b></h3>
<p>For this project, I added:</p>
<ul>
<li>1-channel relay module (3.3V or 5V compatible)</li>
<li>External load (e.g., lamp, fan, or test LED)</li>
</ul>
<h3><b>Relay Wiring</b></h3>
<table>
<tbody>
<tr>
<td><b>Relay Module</b></td>
<td><b>ESP32</b></td>
</tr>
<tr>
<td>VCC</td>
<td>5V or 3.3V</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
</tr>
<tr>
<td>IN</td>
<td>GPIO 26</td>
</tr>
</tbody>
</table>
<p>Note: Some relay modules are <b>active LOW</b>, meaning:</p>
<ul>
<li>LOW → ON</li>
<li>HIGH → OFF</li>
</ul>
<h3><b>Concept Logic</b></h3>
<p>Instead of constantly switching while your hand is present, I use a <b>toggle mechanism</b>:</p>
<ol>
<li>Detect hand within threshold</li>
<li>Toggle relay state</li>
<li>Wait until hand is removed before detecting again</li>
</ol>
<p>This prevents flickering or rapid switching.</p>
<h3><b>Example Code: Touchless Toggle Switch</b></h3>
<pre><pre><code class="language-cpp">#include &lt;Wire.h&gt;
#include &quot;Adafruit_VL53L0X.h&quot;
#define RELAY_PIN 26
#define DIST_THRESHOLD 100  // mm

Adafruit_VL53L0X lox = Adafruit_VL53L0X();
bool relay_state = false;
bool hand_detected = false;

void setup() {
 Serial.begin(115200);
 Wire.begin();

 pinMode(RELAY_PIN, OUTPUT);
 digitalWrite(RELAY_PIN, HIGH); // OFF (for active LOW relay)

 if (!lox.begin()) {
   Serial.println(&quot;Failed to initialize VL53L0X&quot;);
   while (1);
 }

 Serial.println(&quot;Touchless Switch Ready&quot;);

}


void loop() {

 VL53L0X_RangingMeasurementData_t measure;
 lox.rangingTest(&amp;measure, false);

 if (measure.RangeStatus != 4) {
   int distance = measure.RangeMilliMeter;
   Serial.print(&quot;Distance: &quot;);
   Serial.println(distance);

   // Hand detected

   if (distance &lt; DIST_THRESHOLD &amp;&amp; !hand_detected) {
     relay_state = !relay_state;
     digitalWrite(RELAY_PIN, relay_state ? LOW : HIGH);
     Serial.println(relay_state ? &quot;Relay ON&quot; : &quot;Relay OFF&quot;);
     hand_detected = true;
     delay(300); // debounce
   }

   // Hand removed
   if (distance &gt;= DIST_THRESHOLD) {
     hand_detected = false;
   }
 }
 delay(50);
}</code></pre></pre>
<h3><b>Behavior Explanation</b></h3>
<p>Here’s what actually happens when I use it:</p>
<ul>
<li>I move my hand close to the sensor</li>
<li>The ESP32 detects distance &lt; 100 mm</li>
<li>The relay toggles ON</li>
<li>I remove my hand → system resets</li>
<li>Next gesture toggles it OFF</li>
</ul>
<p>It feels very natural—almost like a “gesture button”.</p>
<h3><b>Safety Notes (Important)</b></h3>
<p>If you’re controlling AC devices:</p>
<ul>
<li>Use proper relay modules with isolation</li>
<li>Never touch exposed wiring</li>
<li>Consider using a relay module with optocoupler</li>
</ul>
<p>For testing, I recommend starting with:</p>
<ul>
<li>LED + resistor instead of mains devices</li>
</ul>
<h3><b>Improvements I Usually Add</b></h3>
<p>In real builds, I often extend this by:</p>
<ul>
<li>Adding <b>LED indicator</b> for ON/OFF state</li>
<li>Using <b>moving average filtering</b> for stable detection</li>
<li>Adding <b>sound or buzzer feedback</b></li>
<li>Integrating with <b>WiFi control (web dashboard or MQTT)</b></li>
</ul>
<h3><b>Conclusion</b></h3>
<p>Using a Time-of-Flight sensor with ESP32 is one of the easiest ways to build accurate distance-based projects. Once I started using these sensors, I stopped relying on ultrasonic modules for most indoor applications.</p>
<p>They’re precise, fast, and incredibly easy to integrate.</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-time-of-flight-sensor-vl53l0x/">ESP32 Time-of-Flight Sensor Tutorial (VL53L0X / VL53L1X Distance Measurement)</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Use an NPK Soil Sensor with ESP32</title>
		<link>https://www.teachmemicro.com/how-to-use-an-npk-soil-sensor-with-esp32/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-use-an-npk-soil-sensor-with-esp32</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 00:35:55 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<category><![CDATA[Sensor Tutorial]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=11170</guid>

					<description><![CDATA[<p>If you are building a smart agriculture project, one of the most useful things you can measure is the nutrient condition of the soil. Moisture sensors can tell you if the soil is wet or dry, but they cannot tell you whether the soil still has enough nutrients for healthy plant growth. This is where &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/how-to-use-an-npk-soil-sensor-with-esp32/">How to Use an NPK Soil Sensor with ESP32</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>If you are building a smart agriculture project, one of the most useful things you can measure is the nutrient condition of the soil. Moisture sensors can tell you if the soil is wet or dry, but they cannot tell you whether the soil still has enough nutrients for healthy plant growth. This is where an NPK sensor becomes useful.</p>
<p><span id="more-11170"></span></p>
<p>An NPK sensor measures the levels of Nitrogen (N), Phosphorus (P), and Potassium (K) in soil. These three nutrients are the primary macronutrients needed by plants. In this tutorial, I will show you how to connect an RS485 NPK sensor to an ESP32, read the values using Modbus RTU, and then create a simple web dashboard so you can monitor the readings from your browser without needing to keep the Serial Monitor open.</p>
<h3><strong>What is an NPK sensor?</strong></h3>
<p>An NPK sensor is a soil probe designed to estimate the levels of nitrogen, phosphorus, and potassium present in the soil. These values are commonly reported in mg/kg or in similar concentration-based units depending on the sensor model.</p>
<p><img decoding="async" class="aligncenter" src="https://www.teachmemicro.com/wp-content/uploads/2023/05/npk-sensor.png" /></p>
<p>Most low-cost NPK sensors used in hobby and prototype projects do not output simple analog voltages. Instead, they usually communicate using RS485 and Modbus RTU. Because of this, you cannot connect the sensor directly to a normal ESP32 GPIO pin and expect useful readings. The ESP32 needs help from an RS485-to-TTL converter, usually a MAX485 module or something similar.</p>
<p>This sounds more complicated at first, but once the wiring and Modbus request frames are set up correctly, reading the sensor becomes straightforward.</p>
<p>&nbsp;</p>
<h4><strong>How the sensor communicates</strong></h4>
<p>The type of NPK sensor used in this tutorial communicates over RS485, which is commonly used in industrial devices because it works well over longer cable distances and in electrically noisy environments.</p>
<p>The protocol on top of RS485 is usually Modbus RTU. In simple terms, the ESP32 sends a request asking for a specific register, and the sensor replies with the data stored in that register.</p>
<p><img decoding="async" class="aligncenter" src="https://www.teachmemicro.com/wp-content/uploads/2023/05/DataByte.gif" alt="logic signals - RS232 vs TTL vs RS485" /></p>
<p>For many common NPK sensors, the nutrient registers are as follows:</p>
<blockquote><p>Nitrogen at register 0x001E</p>
<p>Phosphorus at register 0x001F</p>
<p>Potassium at register 0x0020</p></blockquote>
<p>The ESP32 must send a proper Modbus frame including the CRC bytes at the end. The sensor then replies with the value.</p>
<p>&nbsp;</p>
<h3><strong>Materials needed</strong></h3>
<p>You will need the following parts for this project:</p>
<ul>
<li>ESP32 development board</li>
<li>RS485 NPK soil sensor</li>
<li>MAX485 or equivalent RS485-to-TTL converter</li>
<li>Jumper wires</li>
<li>External power supply for the NPK sensor</li>
<li>Breadboard or terminal connections as needed</li>
</ul>
<p>The external power supply is important because many NPK sensors require 9V to 24V, and they will not work correctly from the ESP32’s 3.3V pin.</p>
<p><img decoding="async" class="aligncenter" src="https://www.teachmemicro.com/wp-content/uploads/2023/05/rs485-transceiver-module-1024x683.png" /></p>
<h3><strong>Wiring the NPK sensor to the ESP32</strong></h3>
<p>The RS485 lines from the sensor connect to the A and B terminals of the MAX485 module. The MAX485 then converts the differential RS485 signals into TTL serial that the ESP32 can understand.</p>
<p>The typical connections are as follows.</p>
<h4><strong>NPK sensor to MAX485</strong></h4>
<ul>
<li>Sensor A to MAX485 A</li>
<li>Sensor B to MAX485 B</li>
<li>Sensor VCC to external sensor supply positive</li>
<li>Sensor GND to external sensor supply ground</li>
</ul>
<h4><strong>MAX485 to ESP32</strong></h4>
<ul>
<li>RO to ESP32 RX pin</li>
<li>DI to ESP32 TX pin</li>
<li>RE and DE tied together, then connected to one ESP32 GPIO</li>
<li>VCC to ESP32 3.3V or module-rated logic supply</li>
<li>GND to ESP32 GND</li>
</ul>
<p>For this tutorial, I will use these ESP32 pins:</p>
<ul>
<li>MAX485 RO to GPIO16</li>
<li>MAX485 DI to GPIO17</li>
<li>MAX485 RE+DE to GPIO4</li>
</ul>
<p>The ESP32 and the sensor power supply must share a common ground. Without a common ground, communication may fail or become unstable.</p>
<h4><strong>Why the RE/DE pin matters</strong></h4>
<p>RS485 communication is usually half-duplex. This means the same pair of wires is used for both sending and receiving data, but not at the same time.</p>
<p>The MAX485 module uses the RE and DE pins to control whether it is transmitting or receiving. In many hobby circuits, these two pins are tied together and driven by one ESP32 GPIO.</p>
<p>When the control pin is HIGH, the module transmits. When it is LOW, the module listens for incoming data. So every time the ESP32 sends a Modbus request, it must briefly switch the MAX485 into transmit mode, then immediately switch back to receive mode so it can catch the sensor’s reply.</p>
<p>&nbsp;</p>
<h3><strong>Basic ESP32 code to read NPK values</strong></h3>
<p>Before jumping into a web dashboard, I always prefer starting with the simplest working version. That means reading the values first and printing them to the Serial Monitor. Once that works, it becomes much easier to build additional features on top.</p>
<p>Here is the basic code:</p>
<pre><pre><code class="language-cpp">#include &lt;HardwareSerial.h&gt;

#define RXD2 16
#define TXD2 17
#define RS485_DIR 4

HardwareSerial npk_serial(2);

byte nitrogen_cmd[]   = {0x01, 0x03, 0x00, 0x1E, 0x00, 0x01, 0xE4, 0x0C};
byte phosphorus_cmd[] = {0x01, 0x03, 0x00, 0x1F, 0x00, 0x01, 0xB5, 0xCC};
byte potassium_cmd[]  = {0x01, 0x03, 0x00, 0x20, 0x00, 0x01, 0x85, 0xC0};


void setup() {
  Serial.begin(115200);
  npk_serial.begin(9600, SERIAL_8N1, RXD2, TXD2);
  pinMode(RS485_DIR, OUTPUT);
  digitalWrite(RS485_DIR, LOW);
}

int read_npk_value(byte *command) {
  while (npk_serial.available()) {
    npk_serial.read();
  }

  digitalWrite(RS485_DIR, HIGH);
  delay(10);

  npk_serial.write(command, 8);
  npk_serial.flush();

  digitalWrite(RS485_DIR, LOW);
  delay(20);

 

  byte response[7];
  int index = 0;
  unsigned long start_time = millis();

 

  while ((millis() - start_time) &lt; 1000 &amp;&amp; index &lt; 7) {
    if (npk_serial.available()) {
      response[index++] = npk_serial.read();
    }
  }

 
  if (index == 7) {
    return response[4];
  }

   return -1;

}

 

void loop() {
  int nitrogen = read_npk_value(nitrogen_cmd);
  int phosphorus = read_npk_value(phosphorus_cmd);
  int potassium = read_npk_value(potassium_cmd);

 

  Serial.print(&quot;Nitrogen: &quot;);
  Serial.print(nitrogen);
  Serial.print(&quot; mg/kg, Phosphorus: &quot;);
  Serial.print(phosphorus);
  Serial.print(&quot; mg/kg, Potassium: &quot;);
  Serial.print(potassium);
  Serial.println(&quot; mg/kg&quot;);

  delay(2000);
}</code></pre></pre>
<h4><strong>Understanding the code</strong></h4>
<p>The ESP32 is using HardwareSerial(2) so that we can use a second UART for the sensor. That keeps the main Serial Monitor free for debugging.</p>
<p>Each nutrient has its own Modbus command frame. These command frames already include the sensor address, function code, register address, number of registers to read, and CRC bytes.</p>
<p>The read_npk_value() function performs four important steps:</p>
<p>First, it clears any old bytes from the UART buffer. This prevents leftover data from earlier reads from corrupting the next reading.</p>
<p>Second, it sets the RS485 direction pin HIGH so the MAX485 goes into transmit mode.</p>
<p>Third, it writes the Modbus command to the sensor.</p>
<p>Fourth, it switches the RS485 module back to receive mode and waits for the reply.</p>
<p>If the sensor responds correctly, the function returns the value byte. If not, it returns -1, which is useful for indicating a failed read.</p>
<h4><strong>Expected Serial Monitor output</strong></h4>
<p>If everything is wired correctly, you should see values similar to this:</p>
<blockquote><p>Nitrogen: 32 mg/kg, Phosphorus: 14 mg/kg, Potassium: 56 mg/kg</p>
<p>Nitrogen: 31 mg/kg, Phosphorus: 14 mg/kg, Potassium: 57 mg/kg</p>
<p>Nitrogen: 32 mg/kg, Phosphorus: 15 mg/kg, Potassium: 56 mg/kg</p></blockquote>
<p>The numbers will vary depending on your soil and sensor model. Some cheaper sensors may not be highly accurate, but they are still useful for monitoring trends and comparing one soil condition against another.</p>
<h4><strong>Looking at the Modbus response</strong></h4>
<p>A typical sensor reply might look something like this:</p>
<blockquote><p>01 03 02 00 20 B8 44</p></blockquote>
<p>This can be interpreted as follows:</p>
<ul>
<li>01 is the sensor address</li>
<li>03 is the Modbus function code for reading holding registers</li>
<li>02 means two data bytes follow</li>
<li>00 20 is the returned value in hexadecimal</li>
<li>B8 44 is the CRC</li>
</ul>
<p>Hex 0x0020 is decimal 32, so that would mean the nutrient value is 32.</p>
<p>In the basic example above, I kept things simple and returned response[4], since many sensors return small values that fit in one byte. However, if your sensor returns larger values, you may need to combine both data bytes like this:</p>
<pre>int value = (response[3] &lt;&lt; 8) | response[4];</pre>
<p>&nbsp;</p>
<p>That is the safer approach if you expect readings greater than 255.</p>
<p>&nbsp;</p>
<h3><strong>Adding a web dashboard</strong></h3>
<p>Once I can see valid readings in the Serial Monitor, the next step is making the ESP32 serve those values on a web page. This way, I can open the ESP32 IP address in a browser on my phone or laptop and watch the nutrient values update automatically.</p>
<p>To keep things simple, I will use the built-in WiFi.h and WebServer.h libraries. The browser will periodically request the latest readings from a /data endpoint, and JavaScript on the page will update the displayed values automatically.</p>
<p>This approach is simple, reliable, and easy to understand.</p>
<h4><strong>How the web dashboard works</strong></h4>
<p>The idea is straightforward:</p>
<p>The ESP32 connects to Wi-Fi and starts a web server. The main page contains some HTML and JavaScript. Every two seconds, the JavaScript sends a request to /data. The ESP32 responds with the latest NPK values in JSON format. The browser then updates the page without needing a manual refresh.</p>
<p>So while the sensor continues to be read by the ESP32, the browser becomes a live dashboard.</p>
<h4><strong>ESP32 NPK web dashboard code</strong></h4>
<pre><pre><code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;WebServer.h&gt;
#include &lt;HardwareSerial.h&gt;

#define RXD2 16
#define TXD2 17
#define RS485_DIR 4

const char* ssid = &quot;YOUR_WIFI_NAME&quot;;
const char* password = &quot;YOUR_WIFI_PASSWORD&quot;;

HardwareSerial npk_serial(2);
WebServer server(80);

byte nitrogen_cmd[]   = {0x01, 0x03, 0x00, 0x1E, 0x00, 0x01, 0xE4, 0x0C};
byte phosphorus_cmd[] = {0x01, 0x03, 0x00, 0x1F, 0x00, 0x01, 0xB5, 0xCC};
byte potassium_cmd[]  = {0x01, 0x03, 0x00, 0x20, 0x00, 0x01, 0x85, 0xC0};

int nitrogen_value = -1;
int phosphorus_value = -1;
int potassium_value = -1;

unsigned long last_read_time = 0;
const unsigned long read_interval = 2000;

const char dashboard_html[] PROGMEM = R&quot;rawliteral(
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
  &lt;title&gt;NPK Sensor Dashboard&lt;/title&gt;
  &lt;style&gt;
    body {
      font-family: Arial, sans-serif;
      text-align: center;
      margin: 0;
      padding: 20px;
      background: #f4f4f4;
    }
    .container {
      max-width: 500px;
      margin: auto;
    }
    .card {
      background: white;
      margin: 15px 0;
      padding: 20px;
      border-radius: 12px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .label {
      font-size: 18px;
      color: #555;
    }
    .value {
      font-size: 36px;
      font-weight: bold;
      margin-top: 10px;
    }
    .unit {
      font-size: 16px;
      color: #777;
    }
    .status {
      margin-top: 20px;
      color: #666;
      font-size: 14px;
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div class=&quot;container&quot;&gt;
    &lt;h2&gt;NPK Soil Sensor Dashboard&lt;/h2&gt;
    &lt;div class=&quot;card&quot;&gt;
      &lt;div class=&quot;label&quot;&gt;Nitrogen&lt;/div&gt;
      &lt;div class=&quot;value&quot; id=&quot;nitrogen&quot;&gt;--&lt;/div&gt;
      &lt;div class=&quot;unit&quot;&gt;mg/kg&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;card&quot;&gt;
      &lt;div class=&quot;label&quot;&gt;Phosphorus&lt;/div&gt;
      &lt;div class=&quot;value&quot; id=&quot;phosphorus&quot;&gt;--&lt;/div&gt;
      &lt;div class=&quot;unit&quot;&gt;mg/kg&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;card&quot;&gt;
      &lt;div class=&quot;label&quot;&gt;Potassium&lt;/div&gt;
      &lt;div class=&quot;value&quot; id=&quot;potassium&quot;&gt;--&lt;/div&gt;
      &lt;div class=&quot;unit&quot;&gt;mg/kg&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;status&quot; id=&quot;status&quot;&gt;Waiting for data...&lt;/div&gt;
  &lt;/div&gt;
  &lt;script&gt;
    function updateData() {
      fetch(&#039;/data&#039;)
        .then(response =&gt; response.json())
        .then(data =&gt; {
          document.getElementById(&#039;nitrogen&#039;).innerText = data.nitrogen;
          document.getElementById(&#039;phosphorus&#039;).innerText = data.phosphorus;
          document.getElementById(&#039;potassium&#039;).innerText = data.potassium;
          document.getElementById(&#039;status&#039;).innerText =
            &#039;Last updated: &#039; + new Date().toLocaleTimeString();
        })
        .catch(error =&gt; {
          document.getElementById(&#039;status&#039;).innerText = &#039;Connection error&#039;;
        });
    }
    updateData();
    setInterval(updateData, 2000);
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
)rawliteral&quot;;
 

int read_npk_value(byte *command) {
  while (npk_serial.available()) {
    npk_serial.read();
  }

  digitalWrite(RS485_DIR, HIGH);
  delay(10);

  npk_serial.write(command, 8);
  npk_serial.flush();

  digitalWrite(RS485_DIR, LOW);
  delay(20);

  byte response[7];
  int index = 0;
  unsigned long start_time = millis();

  while ((millis() - start_time) &lt; 1000 &amp;&amp; index &lt; 7) {
    if (npk_serial.available()) {
      response[index++] = npk_serial.read();
    }
  }
 
  if (index == 7) {
    return (response[3] &lt;&lt; 8) | response[4];
  }
 
  return -1;
}

void update_sensor_values() {
  nitrogen_value = read_npk_value(nitrogen_cmd);
  delay(100);

  phosphorus_value = read_npk_value(phosphorus_cmd);
  delay(100);

  potassium_value = read_npk_value(potassium_cmd);
}

void handle_root() {
  server.send(200, &quot;text/html&quot;, dashboard_html);
}

void handle_data() {
  String json = &quot;{&quot;;
  json += &quot;\&quot;nitrogen\&quot;:&quot; + String(nitrogen_value) + &quot;,&quot;;
  json += &quot;\&quot;phosphorus\&quot;:&quot; + String(phosphorus_value) + &quot;,&quot;;
  json += &quot;\&quot;potassium\&quot;:&quot; + String(potassium_value);
  json += &quot;}&quot;;
  server.send(200, &quot;application/json&quot;, json);
}

void setup() {
  Serial.begin(115200);
  npk_serial.begin(9600, SERIAL_8N1, RXD2, TXD2);
  pinMode(RS485_DIR, OUTPUT);
  digitalWrite(RS485_DIR, LOW);
  WiFi.begin(ssid, password);
  Serial.print(&quot;Connecting to WiFi&quot;);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(&quot;.&quot;);
  }

  Serial.println();
  Serial.print(&quot;Connected. ESP32 IP address: &quot;);
  Serial.println(WiFi.localIP());

  update_sensor_values();

  server.on(&quot;/&quot;, handle_root);
  server.on(&quot;/data&quot;, handle_data);
  server.begin();

  Serial.println(&quot;Web server started&quot;);
}

void loop() {
  server.handleClient();

  if (millis() - last_read_time &gt;= read_interval) {
    last_read_time = millis();
    update_sensor_values();
 
    Serial.print(&quot;N: &quot;);
    Serial.print(nitrogen_value);
    Serial.print(&quot;  P: &quot;);
    Serial.print(phosphorus_value);
    Serial.print(&quot;  K: &quot;);
    Serial.println(potassium_value);
  }
}</code></pre></pre>
<h4><strong>What this code adds</strong></h4>
<p>This version builds on the earlier example. It still reads the sensor the same way, but now it also does three additional things.</p>
<p>First, it connects the ESP32 to your Wi-Fi network.</p>
<p>Second, it starts a web server and serves a dashboard page when you visit the ESP32 IP address.</p>
<p>Third, it creates a /data endpoint that sends the current nitrogen, phosphorus, and potassium readings in JSON format.</p>
<p>The JavaScript in the browser requests this JSON every two seconds, so the dashboard updates automatically.</p>
<h4><strong>Viewing the dashboard</strong></h4>
<p>After uploading the code, open the Serial Monitor. Once the ESP32 connects to Wi-Fi, it will print something like this:</p>
<blockquote><p>Connected. ESP32 IP address: 192.168.1.25</p>
<p>Web server started</p></blockquote>
<p>Open that IP address in your browser. You should see a simple dashboard showing the current nitrogen, phosphorus, and potassium values.</p>
<p>As the ESP32 reads new values, the page updates automatically every two seconds. There is no need to refresh the browser manually.</p>
<h4><strong>Why this approach is useful</strong></h4>
<p>A web dashboard makes the project much more practical. Instead of connecting the ESP32 to a computer and opening the Serial Monitor every time, you can simply use your phone or laptop on the same Wi-Fi network.</p>
<p>This is useful for greenhouse monitoring, classroom demonstrations, farm prototypes, and remote sensor nodes where you want a quick way to view live data.</p>
<p>Once this basic dashboard is working, you can later expand it into something more advanced such as charts, historical logging, cloud upload, or mobile notifications.</p>
<p>&nbsp;</p>
<h3><strong>Common problems and fixes</strong></h3>
<p>One of the most common issues is getting -1 values from the sensor. This usually means the ESP32 did not receive a valid response. If this happens, I would first check the A and B lines on the RS485 connection. Some sensors label these differently, so swapping them is often worth trying.</p>
<p>Another common issue is power. Many NPK sensors draw more power than expected and require a stable external supply. If the supply is weak or noisy, the readings may fail or jump around.</p>
<p>If the web page loads but the values do not update, check whether the ESP32 is still reading the sensor properly in the Serial Monitor. If the Serial Monitor values are wrong, the browser will simply display those same wrong values.</p>
<p>If the browser cannot load the dashboard at all, verify that your phone or laptop is on the same Wi-Fi network as the ESP32.</p>
<h4><strong>Possible improvements</strong></h4>
<p>This version is intentionally simple so it is easy to follow. But once you understand the flow, there are several ways to improve it.</p>
<p>You could add color indicators so the dashboard visually shows low, medium, or high nutrient levels. You could also add a chart so you can see how values change over time. Another useful upgrade would be to save the readings into EEPROM, SPIFFS, an SD card, or a cloud database.</p>
<p>If you want smoother real-time updates, you can also replace periodic fetch() requests with WebSockets, but for many projects the current method is already enough.</p>
<p>&nbsp;</p>
<h3><strong>Final thoughts</strong></h3>
<p>Using an NPK sensor with an ESP32 is a very good beginner-to-intermediate project because it introduces several useful topics at once. You learn how to work with RS485, how Modbus RTU communication works, how to read industrial-style sensors, and how to turn the ESP32 into a live monitoring device with a browser-based dashboard.</p>
<p>I like this kind of project because it moves beyond simple LEDs and basic analog sensors. It feels much closer to how real agricultural and industrial monitoring systems are built.</p>
<p>The most important thing is to get the basic serial-reading version working first. Once you can reliably read nitrogen, phosphorus, and potassium values, adding the web dashboard becomes much easier.</p>
<p>The post <a href="https://www.teachmemicro.com/how-to-use-an-npk-soil-sensor-with-esp32/">How to Use an NPK Soil Sensor with ESP32</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ESP32 + INMP441 I2S Microphone with Live Web Graph</title>
		<link>https://www.teachmemicro.com/esp32-inmp441-i2s-microphone-with-live-web-graph/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=esp32-inmp441-i2s-microphone-with-live-web-graph</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Thu, 16 Apr 2026 22:57:19 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=11133</guid>

					<description><![CDATA[<p>In this project, I’ll show you how I interface the ESP32 with the INMP441 I2S microphone module, starting from basic audio capture and then moving toward a more interactive application where the sound is visualized in real time on a web browser. When I first worked with microphones on microcontrollers, I used analog modules. They &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-inmp441-i2s-microphone-with-live-web-graph/">ESP32 + INMP441 I2S Microphone with Live Web Graph</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In this project, I’ll show you how I interface the <b>ESP32</b> with the <b>INMP441 I2S microphone module</b>, starting from basic audio capture and then moving toward a more interactive application where the sound is visualized in real time on a web browser.</p>
<p>When I first worked with microphones on microcontrollers, I used analog modules. They worked, but noise and signal stability were always a concern. The INMP441 solves that by outputting digital audio through I2S. Once I got it working with the ESP32, the readings became much more consistent and reliable.</p>
<p>To make this project more practical, I won’t stop at just reading audio values. I’ll also show how to turn the ESP32 into a web server that displays a live graph of the captured sound—no page refresh needed.</p>
<h3><b>Materials</b></h3>
<ul>
<li style="font-weight: 400;" aria-level="1">ESP32 development board</li>
<li style="font-weight: 400;" aria-level="1">INMP441 I2S microphone module</li>
<li style="font-weight: 400;" aria-level="1">Jumper wires</li>
<li style="font-weight: 400;" aria-level="1">Breadboard</li>
<li style="font-weight: 400;" aria-level="1">Wi-Fi network</li>
</ul>
<h3><b>Understanding the INMP441 and I2S</b></h3>
<p>The INMP441 is a MEMS microphone with a built-in ADC, which means it outputs digital audio instead of an analog voltage. Because of this, the ESP32 uses its I2S peripheral instead of the ADC.</p>
<p>In this setup, the ESP32 acts as the master and generates the clock signals, while the microphone sends audio data back in sync. The key signals involved are the serial clock, word select, and serial data lines.</p>
<p>Although I2S supports stereo, the INMP441 outputs only one channel. The channel depends on the L/R pin connection.</p>
<h3><b>Wiring the ESP32 to INMP441</b></h3>
<p>The connections are simple once you understand the signals. I typically use GPIO26 for the clock, GPIO25 for the word select line, and GPIO33 for the data input.</p>
<p>The L/R pin is connected to ground so the microphone outputs on the left channel. The ESP32 is then configured to read only that channel.</p>
<p>Make sure to power the module using 3.3V. Using 5V can damage it.</p>
<h3><b>Basic Example: Reading Audio Data</b></h3>
<p>Before building anything more advanced, I always start by verifying that the microphone is working correctly. The simplest way to do that is to read the audio samples and print them to the Serial Monitor.</p>
<p>At first glance, the values may look random, but they actually represent the sound waveform.</p>
<h3><b>Example Code</b></h3>
<pre><pre><code class="language-cpp">#include &quot;driver/i2s.h&quot;

#define I2S_WS 25
#define I2S_SD 33
#define I2S_SCK 26

#define I2S_PORT I2S_NUM_0
#define BUFFER_LEN 64

int32_t sBuffer[BUFFER_LEN];

void setup() {

 Serial.begin(115200);

 i2s_config_t i2s_config = {
   .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
   .sample_rate = 16000,
   .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
   .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
   .communication_format = I2S_COMM_FORMAT_I2S,
   .intr_alloc_flags = 0,
   .dma_buf_count = 4,
   .dma_buf_len = BUFFER_LEN
 };

 i2s_pin_config_t pin_config = {
   .bck_io_num = I2S_SCK,
   .ws_io_num = I2S_WS,
   .data_out_num = -1,
   .data_in_num = I2S_SD
 };

 i2s_driver_install(I2S_PORT, &amp;i2s_config, 0, NULL);
 i2s_set_pin(I2S_PORT, &amp;pin_config);

}

void loop() {

 size_t bytes_read;

 i2s_read(I2S_PORT, &amp;sBuffer, sizeof(sBuffer), &amp;bytes_read, portMAX_DELAY);

 for (int i = 0; i &lt; BUFFER_LEN; i++) {
   Serial.println(sBuffer[i]);
 }

}</code></pre></pre>
<h3><b>Visualizing the Data (Serial Plotter)</b></h3>
<p>After uploading the code, I usually switch to the Serial Plotter instead of the Serial Monitor. This makes it much easier to understand what’s going on.</p>
<p>When there is silence, the signal appears relatively flat. As soon as I speak or make noise, the waveform starts to move. Louder sounds produce larger peaks.</p>
<p>This step is important because it confirms that both the wiring and I2S configuration are correct before moving on.</p>
<h3><b>From Raw Audio to Usable Data</b></h3>
<p>At this point, we are reading raw audio samples, but sending all of these directly to a web page would not be practical. The ESP32 would have to transmit thousands of samples per second, which is unnecessary for simple visualization.</p>
<p>Instead, I process a block of samples and compute a single value that represents the sound level. This makes the system more efficient while still capturing how loud the environment is.</p>
<h3><b>Turning the ESP32 into a Web Server</b></h3>
<p>Once I confirmed that the microphone works, I moved on to building a web interface.</p>
<p>The ESP32 connects to Wi-Fi and hosts a web page. When I open that page in a browser, it displays a graph that updates continuously based on the microphone input.</p>
<p>To achieve real-time updates, I use <b>Server-Sent Events (SSE)</b>. This allows the browser to stay connected to the ESP32 and receive new data automatically, without refreshing the page.</p>
<h3><b>How the Live Graph Works</b></h3>
<p>The ESP32 reads a buffer of samples and computes an average magnitude value. This value represents the sound intensity at that moment.</p>
<p>That value is then streamed to the browser. JavaScript on the page receives the data and draws it on a canvas. As new values arrive, the graph shifts and updates continuously.</p>
<p>The result is a smooth, real-time visualization of sound activity.</p>
<h3><b>Full Example: ESP32 Web Server with Live Sound Graph</b></h3>
<pre><pre><code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;WebServer.h&gt;
#include &quot;driver/i2s.h&quot;

#define I2S_WS   25
#define I2S_SD   33
#define I2S_SCK  26

#define I2S_PORT       I2S_NUM_0
#define SAMPLE_BUFFER  128

const char* ssid = &quot;YOUR_WIFI_NAME&quot;;
const char* password = &quot;YOUR_WIFI_PASSWORD&quot;;

WebServer server(80);

int32_t sample_buffer[SAMPLE_BUFFER];

const char webpage[] PROGMEM = R&quot;rawliteral(
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
  &lt;title&gt;ESP32 Sound Graph&lt;/title&gt;
  &lt;style&gt;
    body { font-family: Arial; text-align: center; }
    canvas { border: 1px solid black; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;

  &lt;h3&gt;Live Sound Graph&lt;/h3&gt;
  &lt;canvas id=&quot;graph&quot; width=&quot;600&quot; height=&quot;250&quot;&gt;&lt;/canvas&gt;
  &lt;script&gt;

    const canvas = document.getElementById(&quot;graph&quot;);
    const ctx = canvas.getContext(&quot;2d&quot;);

    const maxPoints = 120;
    let dataPoints = new Array(maxPoints).fill(0);

    function drawGraph() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.beginPath();

      for (let i = 0; i &lt; dataPoints.length; i++) {
        let x = i * (canvas.width / maxPoints);
        let y = canvas.height - dataPoints[i];
        if (i === 0) ctx.moveTo(x, y);
        else ctx.lineTo(x, y);
      }

      ctx.stroke();
    }

    const source = new EventSource(&#039;/events&#039;);

    source.onmessage = function(event) {
      let val = parseInt(event.data);
      if (isNaN(val)) return;

      if (val &gt; 250) val = 250;
      if (val &lt; 0) val = 0;

      dataPoints.push(val);
      dataPoints.shift();
      drawGraph();
    };

  &lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;

)rawliteral&quot;;

void setup_i2s() {

  i2s_config_t config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = 0,
    .dma_buf_count = 4,
    .dma_buf_len = SAMPLE_BUFFER

  };

  i2s_pin_config_t pins = {
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = -1,
    .data_in_num = I2S_SD
  };

  i2s_driver_install(I2S_PORT, &amp;config, 0, NULL);
  i2s_set_pin(I2S_PORT, &amp;pins);
}

int get_sound_level() {
  size_t bytes_read;
  i2s_read(I2S_PORT, sample_buffer, sizeof(sample_buffer), &amp;bytes_read, portMAX_DELAY);

  int samples = bytes_read / sizeof(int32_t);
  uint64_t total = 0;

  for (int i = 0; i &lt; samples; i++) {
    int32_t s = sample_buffer[i] &gt;&gt; 14;
    if (s &lt; 0) s = -s;
    total += s;
  }

  int avg = total / samples;
  return map(avg, 0, 3000, 0, 250);

}

void handle_root() {
  server.send_P(200, &quot;text/html&quot;, webpage);
}


void handle_events() {

  WiFiClient client = server.client();

  client.println(&quot;HTTP/1.1 200 OK&quot;);
  client.println(&quot;Content-Type: text/event-stream&quot;);
  client.println(&quot;Cache-Control: no-cache&quot;);
  client.println(&quot;Connection: keep-alive&quot;);
  client.println();


  while (client.connected()) {

    int level = get_sound_level();

    client.print(&quot;data: &quot;);

    client.print(level);

    client.print(&quot;\n\n&quot;);

    delay(50);

  }

}

void setup() {

  Serial.begin(115200);

  setup_i2s();

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  Serial.println(WiFi.localIP());

  server.on(&quot;/&quot;, handle_root);
  server.on(&quot;/events&quot;, handle_events);
  server.begin();
}

void loop() {
  server.handleClient();
}</code></pre></pre>
<h3><b>Testing the Web Graph</b></h3>
<p>After uploading the code, I check the Serial Monitor for the ESP32’s IP address. Then I open that address in a browser on the same network.</p>
<p>The graph starts updating immediately. When I speak or clap, the graph responds in real time. If needed, I adjust the scaling in the map() function to better match the environment.</p>
<h3><b>Final Thoughts</b></h3>
<p>I always like starting simple—just reading raw data—before moving into more advanced features. It makes debugging much easier and helps build confidence that each part of the system is working correctly.</p>
<p>Once everything is combined, the ESP32 and INMP441 become a powerful platform for audio-based projects. Adding a live web interface makes it even more practical and interactive.</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-inmp441-i2s-microphone-with-live-web-graph/">ESP32 + INMP441 I2S Microphone with Live Web Graph</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ESPectre Tutorial: Motion Detection Using Wi-Fi Signals (ESP32 + Home Assistant)</title>
		<link>https://www.teachmemicro.com/espectre-tutorial-motion-detection-using-wi-fi-signals-esp32-home-assistant/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=espectre-tutorial-motion-detection-using-wi-fi-signals-esp32-home-assistant</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 05 Jan 2026 12:28:31 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=10344</guid>

					<description><![CDATA[<p>Motion detection usually means PIR sensors, cameras, or microphones. ESPectre takes a very different approach: it detects movement by analyzing how the human body disturbs Wi-Fi radio waves. Interestingly, many modern signal-processing systems — from Wi-Fi sensing to broadcast monitoring infrastructures like this Thor Broadcast deployment — rely on analyzing how signals propagate through networks. &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/espectre-tutorial-motion-detection-using-wi-fi-signals-esp32-home-assistant/">ESPectre Tutorial: Motion Detection Using Wi-Fi Signals (ESP32 + Home Assistant)</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Motion detection usually means PIR sensors, cameras, or microphones. <strong>ESPectre</strong> takes a very different approach: it detects movement by analyzing how the human body disturbs Wi-Fi radio waves.</p>
<p>Interestingly, many modern signal-processing systems — from Wi-Fi sensing to broadcast monitoring infrastructures like this <a href="https://thorbroadcast.com/article/nationwide-media-monitoring-network-using-multi-channel-hdmi-iptv-encoders-hdmi-8230.html">Thor Broadcast</a> deployment — rely on analyzing how signals propagate through networks.</p>
<p>In this tutorial, I’ll walk through what ESPectre is, how it works, and how to set it up using an ESP32 and Home Assistant. No cameras, no audio recording, and no machine learning — just physics and signal processing.</p>
<p><span id="more-10344"></span></p>
<hr />
<h3><strong>What Is ESPectre?</strong></h3>
<p><span class="hover:entity-accent entity-underline inline cursor-pointer align-baseline"><span class="whitespace-normal">ESPectre is an open-source motion detection system that runs on an ESP32 and uses <strong>Wi-Fi Channel State Information (CSI)</strong> to detect movement.</span></span></p>
<p>When a person moves through a room, their body changes the way Wi-Fi signals bounce and propagate between a router and the ESP32. ESPectre measures these changes and converts them into a motion signal that can be used for automation or security.</p>
<p>Key characteristics:</p>
<ul>
<li>No cameras</li>
<li>No microphones</li>
<li>Works in total darkness</li>
<li>Preserves privacy</li>
<li>Integrates with Home Assistant via MQTT</li>
</ul>
<hr />
<h3><strong>How Wi-Fi Motion Detection Works</strong></h3>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2026/01/how-espectre-works.avif"><img data-dominant-color="efeff0" data-has-transparency="false" style="--dominant-color: #efeff0;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-10345 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2026/01/how-espectre-works.avif" alt="" width="768" height="512" srcset="https://www.teachmemicro.com/wp-content/uploads/2026/01/how-espectre-works.avif 768w, https://www.teachmemicro.com/wp-content/uploads/2026/01/how-espectre-works-300x200.avif 300w" sizes="auto, (max-width: 768px) 100vw, 768px" /></a></p>
<p>Wi-Fi signals do not travel in a straight line. They reflect off walls, furniture, and people. Modern Wi-Fi chips expose detailed radio measurements known as <strong>Channel State Information (CSI)</strong>.</p>
<p>ESPectre:</p>
<ol>
<li>Captures CSI data from Wi-Fi packets</li>
<li>Stabilizes and filters the signal</li>
<li>Measures variance caused by movement</li>
<li>Produces a real-time motion score</li>
<li>Publishes motion events via MQTT</li>
</ol>
<p>Instead of learning patterns through AI, ESPectre relies on signal statistics such as variance and segmentation. This makes it lightweight and fast enough to run entirely on the ESP32.</p>
<hr />
<h3><strong>Hardware Requirements</strong></h3>
<p>You will need the following:</p>
<ul>
<li>ESP32 board with CSI support
<ul>
<li>ESP32-S3 is recommended</li>
<li>ESP32-C6 also works</li>
</ul>
</li>
<li>A 2.4 GHz Wi-Fi network</li>
<li>USB cable for flashing</li>
<li>Optional external antenna for better range</li>
</ul>
<p>No special router configuration is required.</p>
<hr />
<h3><strong>Software Requirements</strong></h3>
<ul>
<li>ESP-IDF version 6.1</li>
<li>Git</li>
<li>MQTT broker
<ul>
<li>Home Assistant’s built-in broker, or</li>
<li>Mosquitto on a PC, Raspberry Pi, or NAS</li>
</ul>
</li>
<li>Home Assistant (optional but recommended)</li>
</ul>
<hr />
<h3><strong>Cloning the ESPectre Repository</strong></h3>
<p>Open a terminal and clone the project:</p>
<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><code class="whitespace-pre!">git clone https://github.com/francescopace/espectre.git
cd espectre
</code></pre>
</div>
</div>
<p>This repository contains the firmware, configuration options, and tuning documentation.</p>
<hr />
<h3><strong>Setting Up ESP-IDF</strong></h3>
<p>ESPectre is built using Espressif’s official framework.</p>
<p>Make sure:</p>
<ul>
<li>ESP-IDF v6.1 is installed</li>
<li>idf.py is available in your terminal</li>
<li>Your ESP32 drivers are correctly installed</li>
</ul>
<p>Once ESP-IDF is ready, connect your ESP32 via USB.</p>
<hr />
<h3><strong>Configuring the Firmware</strong></h3>
<p>Set the target chip (example for ESP32-S3):</p>
<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><code class="whitespace-pre!">idf.py set-target esp32s3
</code></pre>
</div>
</div>
<p>Open the configuration menu:</p>
<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><code class="whitespace-pre!">idf.py menuconfig
</code></pre>
</div>
</div>
<p>Inside menuconfig, configure:</p>
<ul>
<li>Wi-Fi SSID and password</li>
<li>MQTT broker address</li>
<li>MQTT credentials (if required)</li>
<li>CSI capture settings (leave defaults initially)</li>
</ul>
<p>Save and exit.</p>
<hr />
<h3><strong>Building and Flashing</strong></h3>
<p>Compile the firmware:</p>
<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><code class="whitespace-pre!">idf.py build
</code></pre>
</div>
</div>
<p>Flash it to the ESP32:</p>
<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><code class="whitespace-pre!">idf.py flash
</code></pre>
</div>
</div>
<p>After flashing, monitor the output:</p>
<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><code class="whitespace-pre!">idf.py monitor
</code></pre>
</div>
</div>
<p>If everything is correct, the ESP32 will connect to Wi-Fi and start publishing motion data.</p>
<hr />
<h3><strong>MQTT Integration</strong></h3>
<p>ESPectre communicates using MQTT.</p>
<h4><strong>Using Home Assistant</strong></h4>
<p>If you are running Home Assistant with MQTT enabled:</p>
<ul>
<li>ESPectre uses Home Assistant MQTT auto-discovery</li>
<li>Motion sensors will appear automatically</li>
</ul>
<p>You will typically see:</p>
<ul>
<li>A binary motion sensor</li>
<li>A numeric movement or activity score</li>
</ul>
<p>No YAML configuration is required for basic usage.</p>
<h4><strong>Using a Standalone MQTT Broker</strong></h4>
<p>If you are not using Home Assistant, you can subscribe manually:</p>
<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><code class="whitespace-pre!">mosquitto_sub -t "espectre/#"
</code></pre>
</div>
</div>
<p>This allows you to inspect raw motion data and debug signal behavior.</p>
<hr />
<h3><strong>Sensor Placement Guidelines</strong></h3>
<p>Placement has a major effect on detection quality.</p>
<p>Recommended setup:</p>
<ul>
<li>ESP32 placed 3 to 8 meters from the Wi-Fi router</li>
<li>Height of approximately 1 to 1.5 meters</li>
<li>Avoid metal surfaces and dense electronics</li>
<li>Clear line-of-sight is helpful but not required</li>
</ul>
<p>The router and ESP32 create a radio “field.” Motion is detected when someone crosses or disturbs that field.</p>
<hr />
<h3><strong>Tuning Motion Sensitivity</strong></h3>
<p>ESPectre includes tuning parameters that affect sensitivity and noise rejection.</p>
<p>Common parameters include:</p>
<ul>
<li>Segmentation threshold</li>
<li>Filtering strength</li>
<li>Subcarrier selection</li>
</ul>
<p>Start with default values. Once motion detection is working, adjust thresholds to:</p>
<ul>
<li>Reduce false positives</li>
<li>Ignore pets or fans</li>
<li>Improve responsiveness</li>
</ul>
<p>Tuning is environment-specific and may take some experimentation.</p>
<hr />
<h3><strong>Example Use Cases</strong></h3>
<p>ESPectre can be used for:</p>
<ul>
<li>Turning lights on when someone enters a room</li>
<li>Detecting occupancy without cameras</li>
<li>Privacy-friendly home security</li>
<li>Energy saving automation</li>
<li>Presence detection in offices or bedrooms</li>
</ul>
<p>Because it does not rely on infrared or optics, it works through furniture and in complete darkness.</p>
<hr />
<h3><strong>Troubleshooting Tips</strong></h3>
<p>If no motion is detected:</p>
<ul>
<li>Confirm Wi-Fi connection</li>
<li>Check MQTT broker address</li>
<li>Verify CSI is enabled in firmware</li>
</ul>
<p>If motion is always detected:</p>
<ul>
<li>Increase segmentation threshold</li>
<li>Move ESP32 farther from the router</li>
<li>Reduce environmental noise sources</li>
</ul>
<p>If Home Assistant sensors do not appear:</p>
<ul>
<li>Ensure MQTT discovery is enabled</li>
<li>Restart Home Assistant</li>
<li>Check MQTT logs</li>
</ul>
<hr />
<h3><strong>Final Thoughts</strong></h3>
<p>ESPectre demonstrates that motion detection does not require cameras or microphones. By using Wi-Fi CSI and an ESP32, it offers a low-cost, privacy-respecting alternative that integrates cleanly with Home Assistant.</p>
<p>If you are interested in experimental sensing, smart homes, or signal processing, ESPectre is a project worth exploring.</p>
<p>The post <a href="https://www.teachmemicro.com/espectre-tutorial-motion-detection-using-wi-fi-signals-esp32-home-assistant/">ESPectre Tutorial: Motion Detection Using Wi-Fi Signals (ESP32 + Home Assistant)</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Use LM35 Temperature Sensor with Arduino and ESP32: Complete Guide with Example Projects</title>
		<link>https://www.teachmemicro.com/how-to-use-lm35-temperature-sensor-with-arduino-and-esp32-complete-guide-with-example-projects/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-use-lm35-temperature-sensor-with-arduino-and-esp32-complete-guide-with-example-projects</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 03 Nov 2025 01:00:27 +0000</pubDate>
				<category><![CDATA[Arduino Projects]]></category>
		<category><![CDATA[ESP32 Projects]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=9613</guid>

					<description><![CDATA[<p>Introduction to LM35 Temperature Sensor The LM35 is a precision temperature sensor that provides an analog voltage output proportional to the measured temperature. It's popular among electronics enthusiasts for its simplicity, accuracy, and ease of use with microcontrollers like Arduino and ESP32. In this tutorial, you’ll learn how LM35 works, how to interface it with &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/how-to-use-lm35-temperature-sensor-with-arduino-and-esp32-complete-guide-with-example-projects/">How to Use LM35 Temperature Sensor with Arduino and ESP32: Complete Guide with Example Projects</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h3><strong>Introduction to LM35 Temperature Sensor</strong></h3>
<p>The <strong>LM35</strong> is a precision temperature sensor that provides an analog voltage output proportional to the measured temperature. It's popular among electronics enthusiasts for its <strong>simplicity, accuracy, and ease of use</strong> with microcontrollers like <strong>Arduino and ESP32</strong>.</p>
<p>In this tutorial, you’ll learn how LM35 works, how to interface it with Arduino and ESP32, and how to build <strong>two practical projects</strong> — a basic temperature reader and an IoT-based web server display.</p>
<p><span id="more-9613"></span></p>
<hr />
<h3><strong>What Is an LM35 Sensor?</strong></h3>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-1.avif"><img data-dominant-color="beb4ab" data-has-transparency="true" style="--dominant-color: #beb4ab;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-9786 has-transparency" src="https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-1.avif" alt="lm35" width="512" height="512" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-1.avif 512w, https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-1-300x300.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-1-150x150.avif 150w" sizes="auto, (max-width: 512px) 100vw, 512px" /></a></p>
<p>The <strong>LM35</strong> is an analog temperature sensor that outputs voltage linearly related to the Celsius temperature. It doesn’t require calibration and provides a direct temperature-to-voltage conversion, making it extremely beginner-friendly.</p>
<p><strong>Manufacturer:</strong> Texas Instruments<br />
<strong>Output Type:</strong> Analog (10mV/°C)<br />
<strong>Operating Range:</strong> -55°C to +150°C<br />
<strong>Accuracy:</strong> ±0.5°C (at room temperature)</p>
<hr />
<h3><strong>Key Features and Specifications of LM35</strong></h3>
<div class="_tableContainer_1rjym_1">
<div class="group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse" tabindex="-1">
<table class="w-fit min-w-(--thread-content-width)">
<thead>
<tr>
<th data-col-size="sm">Feature</th>
<th data-col-size="sm">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td data-col-size="sm">Operating Voltage</td>
<td data-col-size="sm">4V to 30V</td>
</tr>
<tr>
<td data-col-size="sm">Output Voltage</td>
<td data-col-size="sm">10 mV per °C</td>
</tr>
<tr>
<td data-col-size="sm">Temperature Range</td>
<td data-col-size="sm">-55°C to +150°C</td>
</tr>
<tr>
<td data-col-size="sm">Accuracy</td>
<td data-col-size="sm">±0.5°C (at 25°C)</td>
</tr>
<tr>
<td data-col-size="sm">Output Type</td>
<td data-col-size="sm">Analog</td>
</tr>
<tr>
<td data-col-size="sm">Power Consumption</td>
<td data-col-size="sm">Low</td>
</tr>
</tbody>
</table>
</div>
</div>
<hr />
<h3><strong>How the LM35 Sensor Works: Principle of Operation</strong></h3>
<p>The LM35 works on the principle that the output voltage changes linearly with temperature.<br />
For every <strong>1°C rise</strong>, the output increases by <strong>10 mV</strong>.</p>
<p>So if the LM35 outputs <strong>250 mV</strong>, the temperature is <strong>25°C</strong>.<br />
Formula:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_b5484f5138f1d97c16e6b62d873cbfc4.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="T(^{0}C) = \frac{V_{out} (in\; mV)}{10}" /></span><script type='math/tex'>T(^{0}C) = \frac{V_{out} (in\; mV)}{10}</script></p>
</div>
</div>
<p>This linear relationship makes the LM35 simple to use — no complex calibration or signal conditioning is required.</p>
<p>That means:</p>
<ul>
<li>0°C → 0V</li>
<li>25°C → 250mV</li>
<li>100°C → 1.0V</li>
</ul>
<p>Since the ESP32 ADC reads voltage in a range (0–3.3V), we can compute the temperature as:</p>
<p style="text-align: center;"><span class='MathJax_Preview'><img src='https://www.teachmemicro.com/wp-content/plugins/latex/cache/tex_2f34b41f3800ba68b54f2a4506d11883.gif' style='vertical-align: middle; border: none; padding-bottom:2px;' class='tex' alt="T(^{0}C)=(\frac{ADC_{value}}{4095})\cdot3.3\cdot100" /></span><script type='math/tex'>T(^{0}C)=(\frac{ADC_{value}}{4095})\cdot3.3\cdot100</script></p>
<hr />
<h3><strong>Understanding the LM35 Pin Configuration and Circuit Diagram</strong></h3>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-pinout.avif"><img data-dominant-color="e6e6e6" data-has-transparency="false" style="--dominant-color: #e6e6e6;" loading="lazy" decoding="async" class="aligncenter size-full wp-image-9789 not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-pinout.avif" alt="LM35 pinout" width="984" height="756" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-pinout.avif 984w, https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-pinout-300x230.avif 300w, https://www.teachmemicro.com/wp-content/uploads/2025/11/lm35-pinout-768x590.avif 768w" sizes="auto, (max-width: 984px) 100vw, 984px" /></a></p>
<h4><strong>LM35 Pinout Explained</strong></h4>
<div class="_tableContainer_1rjym_1">
<div class="group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse" tabindex="-1">
<table class="w-fit min-w-(--thread-content-width)">
<thead>
<tr>
<th data-col-size="sm">Pin</th>
<th data-col-size="sm">Function</th>
<th data-col-size="sm">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td data-col-size="sm">1</td>
<td data-col-size="sm">VCC</td>
<td data-col-size="sm">Connects to 5V (Arduino) or 3.3V (ESP32)</td>
</tr>
<tr>
<td data-col-size="sm">2</td>
<td data-col-size="sm">VOUT</td>
<td data-col-size="sm">Analog output voltage</td>
</tr>
<tr>
<td data-col-size="sm">3</td>
<td data-col-size="sm">GND</td>
<td data-col-size="sm">Ground connection</td>
</tr>
</tbody>
</table>
</div>
</div>
<hr />
<h4><strong>LM35 Wiring and Power Requirements</strong></h4>
<ul>
<li>For <strong>Arduino</strong>, power it with <strong>5V</strong>.</li>
<li>For <strong>ESP32</strong>, use <strong>3.3V</strong>.</li>
<li>Always use <strong>a common ground</strong> between LM35 and the microcontroller.</li>
<li>Optionally, add a <strong>0.1 µF capacitor</strong> between VOUT and GND to stabilize readings.</li>
</ul>
<hr />
<h3><strong>Using LM35 with Arduino: Step-by-Step Guide</strong></h3>
<h4><strong>Components Required for Arduino + LM35 Project</strong></h4>
<ul>
<li>Arduino Uno (or Nano)</li>
<li>LM35 temperature sensor</li>
<li>Jumper wires</li>
<li>Breadboard</li>
<li>(Optional) 16x2 LCD display</li>
</ul>
<hr />
<h4><strong>Circuit Diagram for LM35 with Arduino</strong></h4>
<p><a href="https://www.teachmemicro.com/wp-content/uploads/2025/11/arduino-lm35.avif"><img data-dominant-color="97acba" data-has-transparency="false" style="--dominant-color: #97acba;" loading="lazy" decoding="async" class="aligncenter wp-image-9787 size-full not-transparent" src="https://www.teachmemicro.com/wp-content/uploads/2025/11/arduino-lm35.avif" alt="Arduino and LM35 wiring diagram" width="613" height="377" srcset="https://www.teachmemicro.com/wp-content/uploads/2025/11/arduino-lm35.avif 613w, https://www.teachmemicro.com/wp-content/uploads/2025/11/arduino-lm35-300x185.avif 300w" sizes="auto, (max-width: 613px) 100vw, 613px" /></a></p>
<p><strong>Connections:</strong></p>
<ul>
<li>LM35 VCC → 5V</li>
<li>LM35 GND → GND</li>
<li>LM35 VOUT → A0 (analog pin)</li>
</ul>
<hr />
<h4><strong>Arduino Code to Read Temperature from LM35</strong></h4>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"></div>
</div>
</div>
<div class="overflow-y-auto p-4" dir="ltr">
<pre><pre><code class="language-cpp">const int sensorPin = A0;
float temperature;

void setup() {
  Serial.begin(9600);
}

void loop() {
  int sensorValue = analogRead(sensorPin);
  float voltage = (sensorValue / 1023.0) * 5.0;
  temperature = voltage * 100; // 10mV per degree Celsius
  Serial.print(&quot;Temperature: &quot;);
  Serial.print(temperature);
  Serial.println(&quot; °C&quot;);
  delay(1000);
}</code></pre></pre>
</div>
</div>
<hr />
<h4><strong>Displaying Temperature on Serial Monitor or LCD</strong></h4>
<ul>
<li>Open <strong>Serial Monitor</strong> (Ctrl + Shift + M).</li>
<li>You’ll see continuous readings like:
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs"></div>
</div>
</div>
<div class="overflow-y-auto p-4" dir="ltr">Temperature: 27.35 °C<br />
Temperature: 27.42 °C</div>
<div dir="ltr"></div>
</div>
</li>
<li>To display it on an <strong>LCD</strong>, use the <a href="https://www.teachmemicro.com/arduino-lcd-tutorial/"><em>LiquidCrystal</em> library</a> and connect pins RS, E, D4–D7 accordingly.</li>
</ul>
<hr />
<h3><strong>Using LM35 with ESP32: IoT-Based Temperature Monitoring</strong></h3>
<h4><strong>Components Required for ESP32 + LM35 Project</strong></h4>
<ul>
<li>ESP32 board</li>
<li>LM35 temperature sensor</li>
<li>Breadboard and jumper wires</li>
<li>(Optional) Wi-Fi network for IoT display</li>
</ul>
<hr />
<h4><strong>Circuit Diagram for LM35 with ESP32</strong></h4>
<p><strong>Connections:</strong></p>
<ul>
<li>LM35 VCC → 3.3V</li>
<li>LM35 GND → GND</li>
<li>LM35 VOUT → GPIO34 (analog input)</li>
</ul>
<hr />
<h4><strong>ESP32 Code to Read Temperature from LM35</strong></h4>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><pre><code class="language-cpp">const int sensorPin = 34;
float temperature;

void setup() {
  Serial.begin(115200);
}

void loop() {
  int sensorValue = analogRead(sensorPin);
  float voltage = (sensorValue / 4095.0) * 3.3;
  temperature = voltage * 100;
  Serial.print(&quot;Temperature: &quot;);
  Serial.print(temperature);
  Serial.println(&quot; °C&quot;);
  delay(2000);
}</code></pre></pre>
</div>
</div>
<hr />
<h4><strong>Displaying Temperature on a Web Server (IoT Project)</strong></h4>
<p>Below is a full example code that:</p>
<ol>
<li>Reads temperature from LM35.</li>
<li>Connects to Wi-Fi.</li>
<li>Hosts a simple webpage showing live temperature readings.</li>
</ol>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<pre><pre><code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;WebServer.h&gt;

// Replace with your Wi-Fi credentials
const char* ssid = &quot;YOUR_WIFI_SSID&quot;;
const char* password = &quot;YOUR_WIFI_PASSWORD&quot;;

const int lm35_pin = 34; // Analog pin for LM35
WebServer server(80);    // Web server on port 80

float read_temperature() {
  int adc_value = analogRead(lm35_pin);
  float voltage = (adc_value / 4095.0) * 3.3;  // Convert ADC reading to voltage
  float temperature_c = voltage * 100;         // 10mV per °C
  return temperature_c;
}


String html_page(float temp_c) {
  String html = &quot;&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta name=&#039;viewport&#039; content=&#039;width=device-width, initial-scale=1.0&#039;&gt;&quot;;
  html += &quot;&lt;title&gt;ESP32 LM35 Temperature&lt;/title&gt;&quot;;
  html += &quot;&lt;style&gt;body{font-family:Arial;text-align:center;background:#111;color:#0ff;}h1{color:#00ffff;}&lt;/style&gt;&quot;;
  html += &quot;&lt;/head&gt;&lt;body&gt;&quot;;
  html += &quot;&lt;h1&gt;ESP32 LM35 Temperature Monitor&lt;/h1&gt;&quot;;
  html += &quot;&lt;h2&gt;Current Temperature: &quot; + String(temp_c, 2) + &quot; &deg;C&lt;/h2&gt;&quot;;
  html += &quot;&lt;meta http-equiv=&#039;refresh&#039; content=&#039;2&#039;&gt;&quot;; // auto-refresh every 2 seconds
  html += &quot;&lt;/body&gt;&lt;/html&gt;&quot;;
  return html;
}


void handle_root() {
  float temp = read_temperature();
  server.send(200, &quot;text/html&quot;, html_page(temp));
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.print(&quot;Connecting to Wi-Fi&quot;);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(&quot;.&quot;);
  }
  Serial.println(&quot;\nConnected!&quot;);
  Serial.print(&quot;ESP32 IP address: &quot;);
  Serial.println(WiFi.localIP());

  server.on(&quot;/&quot;, handle_root);
  server.begin();
  Serial.println(&quot;Web server started!&quot;);
}

void loop() {
  server.handleClient();
}</code></pre></pre>
</div>
<hr />
<h4>Accessing the Web Page</h4>
<ol>
<li>Upload the sketch to your ESP32.</li>
<li>Open the Serial Monitor at 115200 baud.</li>
<li>Wait until you see:
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9"></div>
<div class="overflow-y-auto p-4" dir="ltr">Connected!<br />
ESP32 IP <span class="hljs-selector-tag">address</span>: <span class="hljs-number">192.168</span>.<span class="hljs-number">1</span>.xxx</div>
</div>
</li>
<li>Open that IP in your browser (e.g., <em>http://192.168.1.45</em>)</li>
<li>You’ll see a <strong>temperature dashboard</strong> refreshing every 2 seconds!</li>
</ol>
<hr />
<h3><strong>Troubleshooting Common Issues</strong></h3>
<h4><strong>Incorrect Temperature Readings</strong></h4>
<ul>
<li>Check voltage reference (5V or 3.3V).</li>
<li>Ensure correct analog pin.</li>
<li>Add a capacitor between VOUT and GND to reduce noise.</li>
</ul>
<h4><strong>Sensor Noise and Calibration Tips</strong></h4>
<ul>
<li>Use shielded cables for long distances.</li>
<li>Calibrate by comparing with a digital thermometer.</li>
</ul>
<hr />
<h3><strong>Applications of LM35 in Real-Life Projects</strong></h3>
<h4><strong>Home Automation Systems</strong></h4>
<ul>
<li>Temperature-controlled fans or air conditioners.</li>
</ul>
<h4><strong>Environmental Monitoring and IoT Systems</strong></h4>
<ul>
<li>Smart agriculture</li>
<li>Weather monitoring stations</li>
<li>IoT dashboards (via Blynk, ThingSpeak, etc.)</li>
</ul>
<hr />
<h3><strong>Advantages and Limitations of LM35 Sensor</strong></h3>
<h4><strong>Benefits of Using LM35</strong></h4>
<ul>
<li>Linear and accurate output</li>
<li>Low cost and easy to interface</li>
<li>No external calibration needed</li>
</ul>
<h4><strong>Limitations and Alternatives</strong></h4>
<ul>
<li>Analog-only output (needs ADC)</li>
<li>Not waterproof</li>
<li>Alternatives: DHT11, DS18B20, TMP36</li>
</ul>
<hr />
<h3><strong>FAQs: How to Use LM35 Temperature Sensor with Arduino and ESP32</strong></h3>
<p><strong>1. What voltage does LM35 require?</strong><br />
It operates between <strong>4V and 30V</strong>, commonly powered by 5V (Arduino) or 3.3V (ESP32).</p>
<p><strong>2. Can I use LM35 with ESP8266 or Raspberry Pi?</strong><br />
Yes, but ensure you use analog-to-digital conversion, as some boards (like ESP8266) have limited ADC support.</p>
<p><strong>3. Why are my readings unstable?</strong><br />
Add a <strong>capacitor (0.1 µF)</strong> across output and ground to filter noise.</p>
<p><strong>4. How accurate is the LM35 sensor?</strong><br />
±0.5°C at room temperature and ±1°C across a wide range.</p>
<p><strong>5. Can LM35 measure below 0°C?</strong><br />
Yes, but output voltage may go negative, requiring offset circuitry.</p>
<p><strong>6. Which IoT platforms support LM35 + ESP32 projects?</strong><br />
Popular ones include <strong>Blynk</strong>, <strong>ThingSpeak</strong>, and <strong>Google Firebase</strong>.</p>
<hr />
<h3><strong>Conclusion</strong></h3>
<p>The <strong>LM35 temperature sensor</strong> is a simple, reliable, and precise device for temperature measurement and monitoring. Whether you’re using <strong>Arduino for basic sensor readings</strong> or <strong>ESP32 for IoT web dashboards</strong>, the LM35 offers versatility and accuracy.</p>
<p>Experimenting with LM35 is an excellent way to start learning about <strong>analog sensors, ADC conversion, and IoT data visualization</strong>.</p>
<p>For more information, visit the <a class="decorated-link cursor-pointer" target="_new" rel="noopener">Texas Instruments LM35 Datasheet</a>.</p>
<p>The post <a href="https://www.teachmemicro.com/how-to-use-lm35-temperature-sensor-with-arduino-and-esp32-complete-guide-with-example-projects/">How to Use LM35 Temperature Sensor with Arduino and ESP32: Complete Guide with Example Projects</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ESP32 Earthquake Monitor with USGS API, GPS, LCD, and Web Dashboard</title>
		<link>https://www.teachmemicro.com/esp32-earthquake-monitor-with-usgs-api-gps-lcd-and-web-dashboard/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=esp32-earthquake-monitor-with-usgs-api-gps-lcd-and-web-dashboard</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 20 Oct 2025 01:00:00 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=9630</guid>

					<description><![CDATA[<p>Was that an earthquake, or did I just stand up too fast? If you’ve ever asked that question — then this project’s for you. We’re building an ESP32 Earthquake Monitor that fetches live data from the USGS Earthquake API, locates your position via NEO-6M GPS, and alerts you through an LCD, buzzer, and RGB LED. &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-earthquake-monitor-with-usgs-api-gps-lcd-and-web-dashboard/">ESP32 Earthquake Monitor with USGS API, GPS, LCD, and Web Dashboard</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><em>Was that an earthquake, or did I just stand up too fast?</em> If you’ve ever asked that question — then this project’s for you.</p>
<p>We’re building an <strong>ESP32 Earthquake Monitor</strong> that fetches live data from the <strong>USGS Earthquake API</strong>, locates your position via <strong>NEO-6M GPS</strong>, and alerts you through an <strong>LCD</strong>, <strong>buzzer</strong>, and <strong>RGB LED</strong>. It even logs quake data to an <strong>SD card</strong> and hosts a <strong>mini web dashboard</strong> for quake history — because if you’re going to feel the earth move, you might as well have the data to prove it.</p>
<p><span id="more-9630"></span></p>
<hr />
<h3>Project Overview</h3>
<p>This project combines <strong>IoT</strong>, <strong>geolocation</strong>, and <strong>real-time data processing</strong> in one neat ESP32 package.</p>
<p>Here’s what happens behind the scenes:</p>
<ol>
<li>The <strong>ESP32</strong> connects to Wi-Fi and syncs its clock with an <strong>NTP (Network Time Protocol)</strong> server.</li>
<li>Using the current date, it requests the day’s earthquakes in a given location via the <a href="https://earthquake.usgs.gov/fdsnws/event/1/"><strong>USGS Earthquake API</strong></a>.</li>
<li>The <a href="https://www.teachmemicro.com/arduino-gps-tutorial/"><strong>NEO-6M GPS module</strong></a> provides your current coordinates.</li>
<li>The ESP32 computes which earthquakes are <strong>closest</strong> to you.</li>
<li>If a quake is detected within ~200 km:
<ul>
<li>The <a href="https://www.teachmemicro.com/arduino-i2c-lcd/"><strong>LCD</strong></a> shows the event.</li>
<li>The <strong>buzzer</strong> sounds an alarm.</li>
<li>The <a href="https://www.teachmemicro.com/arduino-rgb-led-tutorial/"><strong>RGB LED</strong></a> turns <strong>red</strong> (otherwise it stays <strong>green</strong>).</li>
</ul>
</li>
<li>All quake data is logged to the <a href="https://www.teachmemicro.com/using-microsd-breakout-board-arduino/"><strong>SD card</strong></a>, and you can view it later through the <strong>ESP32’s <a href="https://www.teachmemicro.com/sensor-display-on-esp32-web-server/">built-in web page</a></strong>.</li>
</ol>
<hr />
<h3>Components Needed</h3>
<div class="_tableContainer_1rjym_1">
<div class="group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse" tabindex="-1">
<table class="w-fit min-w-(--thread-content-width)">
<thead>
<tr>
<th data-col-size="sm">Component</th>
<th data-col-size="md">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td data-col-size="sm"><strong>ESP32 Dev Board</strong></td>
<td data-col-size="md">Main microcontroller with Wi-Fi</td>
</tr>
<tr>
<td data-col-size="sm"><strong>NEO-6M GPS Module</strong></td>
<td data-col-size="md">Provides latitude &amp; longitude</td>
</tr>
<tr>
<td data-col-size="sm"><strong>16x2 I²C LCD</strong></td>
<td data-col-size="md">Displays quake status</td>
</tr>
<tr>
<td data-col-size="sm"><strong>Active Buzzer</strong></td>
<td data-col-size="md">Alarm sound for nearby quakes</td>
</tr>
<tr>
<td data-col-size="sm"><strong>RGB LED</strong></td>
<td data-col-size="md">Visual indicator (green = calm, red = alert)</td>
</tr>
<tr>
<td data-col-size="sm"><strong>MicroSD Module</strong></td>
<td data-col-size="md">Stores quake history</td>
</tr>
<tr>
<td data-col-size="sm"><strong>Breadboard + wires</strong></td>
<td data-col-size="md">For connections</td>
</tr>
</tbody>
</table>
</div>
</div>
<hr />
<h3>Fetching Data from the USGS Earthquake API</h3>
<p>We’ll use this endpoint to get the latest quakes in the Philippines (my country):</p>
<pre><pre><code class="language-cpp">https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson
&amp;starttime=2025-10-17
&amp;endtime=2025-10-18
&amp;minmagnitude=4.5
&amp;minlatitude=5.0
&amp;maxlatitude=20.0
&amp;minlongitude=115.0
&amp;maxlongitude=130.0
&amp;orderby=time</code></pre></pre>
<p>But instead of fixed dates, we’ll dynamically replace starttime and endtime each day using the <strong>ESP32’s NTP-synced clock</strong>.</p>
<p>That means the device automatically fetches <em>today’s earthquakes</em> every 24 hours — no manual editing required.</p>
<hr />
<h3>GPS and Connections</h3>
<div class="_tableContainer_1rjym_1">
<div class="group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse" tabindex="-1">
<table class="w-fit min-w-(--thread-content-width)">
<thead>
<tr>
<th data-col-size="sm">Module</th>
<th data-col-size="sm">ESP32 Pin</th>
<th data-col-size="sm">Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td data-col-size="sm">NEO-6M TX</td>
<td data-col-size="sm">GPIO16 (RX2)</td>
<td data-col-size="sm">GPS data to ESP32</td>
</tr>
<tr>
<td data-col-size="sm">NEO-6M RX</td>
<td data-col-size="sm">GPIO17 (TX2)</td>
<td data-col-size="sm">Optional</td>
</tr>
<tr>
<td data-col-size="sm">LCD SDA</td>
<td data-col-size="sm">GPIO21</td>
<td data-col-size="sm">I²C</td>
</tr>
<tr>
<td data-col-size="sm">LCD SCL</td>
<td data-col-size="sm">GPIO22</td>
<td data-col-size="sm">I²C</td>
</tr>
<tr>
<td data-col-size="sm">Buzzer +</td>
<td data-col-size="sm">GPIO25</td>
<td data-col-size="sm">Output</td>
</tr>
<tr>
<td data-col-size="sm">RGB LED R</td>
<td data-col-size="sm">GPIO26</td>
<td data-col-size="sm">Red pin</td>
</tr>
<tr>
<td data-col-size="sm">RGB LED G</td>
<td data-col-size="sm">GPIO27</td>
<td data-col-size="sm">Green pin</td>
</tr>
<tr>
<td data-col-size="sm">SD CS</td>
<td data-col-size="sm">GPIO5</td>
<td data-col-size="sm">SD card chip select</td>
</tr>
<tr>
<td data-col-size="sm">SD MOSI</td>
<td data-col-size="sm">GPIO23</td>
<td data-col-size="sm">SPI data (ESP32 → SD)</td>
</tr>
<tr>
<td data-col-size="sm">SD MISO</td>
<td data-col-size="sm">GPIO19</td>
<td data-col-size="sm">SPI data (SD → ESP32)</td>
</tr>
<tr>
<td data-col-size="sm">SD SCK</td>
<td data-col-size="sm">GPIO18</td>
<td data-col-size="sm">SPI clock</td>
</tr>
</tbody>
</table>
</div>
</div>
<hr />
<h3>Code Overview</h3>
<p>Here’s the project flow summarized:</p>
<ol>
<li>Connect to Wi-Fi and get time from NTP.</li>
<li>Format today’s date into <em>YYYY-MM-DD</em>.</li>
<li>Construct the API URL dynamically.</li>
<li>Fetch and parse the GeoJSON data.</li>
<li>Get your GPS location (latitude &amp; longitude).</li>
<li>Compute the distance between your location and each quake’s epicenter using the <a href="https://en.wikipedia.org/wiki/Haversine_formula"><strong>Haversine formula</strong></a>.</li>
<li>Update the LCD, RGB LED, and buzzer accordingly.</li>
<li>Log quake data to the SD card.</li>
<li>Host a simple web page displaying recent logs.</li>
</ol>
<hr />
<h3>Example Code Snippet (Simplified)</h3>
<pre><pre><code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;HTTPClient.h&gt;
#include &lt;ArduinoJson.h&gt;
#include &lt;Wire.h&gt;
#include &lt;LiquidCrystal_I2C.h&gt;
#include &lt;TinyGPSPlus.h&gt;
#include &lt;HardwareSerial.h&gt;
#include &lt;SD.h&gt;
#include &lt;NTPClient.h&gt;
#include &lt;WiFiUdp.h&gt;

#define BUZZER_PIN 25
#define LED_R 26
#define LED_G 27
#define SD_CS 5

LiquidCrystal_I2C lcd(0x27, 16, 2);
HardwareSerial gpsSerial(2);
TinyGPSPlus gps;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, &quot;pool.ntp.org&quot;, 28800); // +8 GMT (Philippines)

const char* ssid = &quot;YOUR_WIFI_SSID&quot;;
const char* password = &quot;YOUR_WIFI_PASSWORD&quot;;

void setup() {
  Serial.begin(115200);
  lcd.init(); lcd.backlight();
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);

  gpsSerial.begin(9600, SERIAL_8N1, 16, 17);

  WiFi.begin(ssid, password);
  lcd.print(&quot;Connecting WiFi...&quot;);
  while (WiFi.status() != WL_CONNECTED) delay(500);

  timeClient.begin();
  while(!timeClient.update()) timeClient.forceUpdate();

  if(!SD.begin(SD_CS)) lcd.print(&quot;SD Fail!&quot;); else lcd.print(&quot;SD Ready&quot;);
  delay(2000);
  lcd.clear();

  fetchEarthquakes();
}

void fetchEarthquakes() {
  // Get today&#039;s date
  time_t epoch = timeClient.getEpochTime();
  struct tm *tm_info = localtime(&amp;epoch);
  char date_str[11];
  strftime(date_str, 11, &quot;%Y-%m-%d&quot;, tm_info);

  String url = &quot;https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&quot;;
  url += &quot;&amp;starttime=&quot; + String(date_str);
  url += &quot;&amp;endtime=&quot; + String(date_str);
  url += &quot;&amp;minmagnitude=4.5&amp;minlatitude=5.0&amp;maxlatitude=20.0&amp;minlongitude=115.0&amp;maxlongitude=130.0&amp;orderby=time&quot;;

  HTTPClient http;
  http.begin(url);
  int code = http.GET();
  if(code == 200) {
    DynamicJsonDocument doc(16384);
    deserializeJson(doc, http.getString());

    for(JsonObject f : doc[&quot;features&quot;].as&lt;JsonArray&gt;()) {
      double lon = f[&quot;geometry&quot;][&quot;coordinates&quot;][0];
      double lat = f[&quot;geometry&quot;][&quot;coordinates&quot;][1];
      double mag = f[&quot;properties&quot;][&quot;mag&quot;];
      const char* place = f[&quot;properties&quot;][&quot;place&quot;];

      double myLat = gps.location.lat();
      double myLon = gps.location.lng();

      double dist = TinyGPSPlus::distanceBetween(myLat, myLon, lat, lon) / 1000.0;
      logToSD(place, mag, dist);

      if(dist &lt; 200) alertUser(place, mag);
    }
  }
  http.end();
}

void alertUser(const char* place, double mag) {
  lcd.clear();
  lcd.print(&quot;ALERT!&quot;);
  lcd.setCursor(0,1);
  lcd.print(place);
  digitalWrite(LED_R, HIGH);
  digitalWrite(LED_G, LOW);
  tone(BUZZER_PIN, 1000, 2000);
  delay(5000);
  noTone(BUZZER_PIN);
  digitalWrite(LED_R, LOW);
  digitalWrite(LED_G, HIGH);
}

void logToSD(const char* place, double mag, double dist) {
  File f = SD.open(&quot;/quakes.csv&quot;, FILE_APPEND);
  if(f) {
    f.printf(&quot;%s,%.1f,%.1f km\n&quot;, place, mag, dist);
    f.close();
  }
}</code></pre></pre>
<hr />
<h3>Adding a Web Dashboard</h3>
<p>With the ESP32’s Wi-Fi capability, it’s easy to serve quake logs over a local webpage.</p>
<p>The idea:</p>
<ul>
<li>The ESP32 starts a web server.</li>
<li>When a user visits the ESP32’s IP address (e.g., <em>http://192.168.1.25</em>), the page displays the contents of <em>quakes.csv</em> from the SD card.</li>
<li>The page auto-refreshes every 60 seconds, so you always see the latest events.</li>
</ul>
<pre><pre><code class="language-cpp">#include &lt;WebServer.h&gt;

WebServer server(80);

void handleRoot() {
  if (!SD.exists(&quot;/quakes.csv&quot;)) {
    server.send(200, &quot;text/html&quot;, &quot;&lt;h3&gt;No quake data logged yet.&lt;/h3&gt;&quot;);
    return;
  }

  File file = SD.open(&quot;/quakes.csv&quot;);
  if (!file) {
    server.send(500, &quot;text/html&quot;, &quot;Error opening quake log.&quot;);
    return;
  }

  String html = &quot;&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&#039;UTF-8&#039;&gt;&quot;;
	html += &quot;&lt;meta http-equiv=&#039;refresh&#039; content=&#039;60&#039;&gt;&quot;;
	html += &quot;&lt;title&gt;ESP32 Earthquake Monitor&lt;/title&gt;&quot;;
	html += &quot;&lt;style&gt;body{font-family:Arial;background:#f8f9fa;color:#333;text-align:center;}&quot;;
	html += &quot;table{margin:auto;border-collapse:collapse;width:80%;}&quot;;
	html += &quot;th,td{border:1px solid #aaa;padding:8px;}th{background:#004b87;color:white;}&lt;/style&gt;&lt;/head&gt;&lt;body&gt;&quot;;
	html += &quot;&lt;h1&gt;ESP32 Earthquake Log&lt;/h1&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Location&lt;/th&gt;&lt;th&gt;Magnitude&lt;/th&gt;&lt;th&gt;Distance (km)&lt;/th&gt;&lt;/tr&gt;&quot;;


  while (file.available()) {
    String line = file.readStringUntil(&#039;\n&#039;);
    int firstComma = line.indexOf(&#039;,&#039;);
    int secondComma = line.indexOf(&#039;,&#039;, firstComma + 1);
    if (firstComma &gt; 0 &amp;&amp; secondComma &gt; 0) {
      String place = line.substring(0, firstComma);
      String mag = line.substring(firstComma + 1, secondComma);
      String dist = line.substring(secondComma + 1);
      html += &quot;&lt;tr&gt;&lt;td&gt;&quot; + place + &quot;&lt;td&gt;&lt;td&gt;&quot; + mag + &quot;&lt;/td&gt;&lt;td&gt;&quot; + dist + &quot;&lt;/td&gt;&lt;/tr&gt;&quot;;
    }
  }
  file.close();

  html += &quot;&lt;/table&gt;&lt;p&gt;&lt;em&gt;Auto-refreshes every 60 seconds&lt;/em&gt;&lt;/p&gt;&lt;body&gt;&lt;/html&gt;&quot;;
  server.send(200, &quot;text/html&quot;, html);
}

void startWebServer() {
  server.on(&quot;/&quot;, handleRoot);
  server.begin();
  Serial.println(&quot;Web server started. Access via http://&quot; + WiFi.localIP().toString());
}

void loop() {
  gpsSerial.listen();
  while (gpsSerial.available()) gps.encode(gpsSerial.read());

  server.handleClient();
}</code></pre></pre>
<h3>How it works</h3>
<ol>
<li><strong><em>WebServer server(80);</em></strong> creates a basic web server on port 80.</li>
<li>The <strong><em>handleRoot()</em></strong> function reads <em>quakes.csv</em> and formats it into an HTML table.</li>
<li>The <strong><em>meta refresh</em></strong> tag reloads the page every minute automatically.</li>
<li>The <strong><em>startWebServer()</em></strong> function launches the web interface after Wi-Fi connects and SD initializes.</li>
</ol>
<p>To start the dashboard, just call this inside <em>setup()</em> after SD initialization:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<pre><pre><code class="language-cpp">startWebServer();</code></pre></pre>
</div>
</div>
<p>When you connect to your ESP32’s IP (check Serial Monitor), you’ll see a simple HTML table listing quake records stored on the SD card:</p>
<div class="_tableContainer_1rjym_1">
<div class="group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse" tabindex="-1">
<table class="w-fit min-w-(--thread-content-width)">
<thead>
<tr>
<th data-col-size="sm">Date</th>
<th data-col-size="sm">Location</th>
<th data-col-size="sm">Magnitude</th>
<th data-col-size="sm">Distance</th>
</tr>
</thead>
<tbody>
<tr>
<td data-col-size="sm">2025-10-18</td>
<td data-col-size="sm">Davao Region</td>
<td data-col-size="sm">5.7</td>
<td data-col-size="sm">120 km</td>
</tr>
<tr>
<td data-col-size="sm">2025-10-18</td>
<td data-col-size="sm">Mindoro Strait</td>
<td data-col-size="sm">4.9</td>
<td data-col-size="sm">450 km</td>
</tr>
</tbody>
</table>
</div>
</div>
<p>The full code can be downloaded here: <a href="https://github.com/rppelayo/esp32-earthquake">github repo</a></p>
<h3>Notes</h3>
<ul>
<li>You can open the page from any device on the same Wi-Fi network (phone, laptop, tablet).</li>
<li>To clear old logs, simply remove or format the SD card.</li>
<li>You can expand this with JavaScript charting (like Chart.js) for visual magnitude trends later.</li>
</ul>
<hr />
<h3>Visual Indicators</h3>
<ul>
<li><strong>RGB LED Green</strong> — No nearby quakes.</li>
<li><strong>RGB LED Red + Buzzer</strong> — Earthquake within ~200 km radius.</li>
</ul>
<p>You can tweak the threshold or add <strong>yellow</strong> for “moderate distance.”</p>
<hr />
<h3>Bonus Improvements</h3>
<ul>
<li>Add a <strong>real-time clock (RTC)</strong> backup if NTP isn’t reachable.</li>
<li>Include <strong>magnitude filtering</strong> (e.g., only warn for M≥5.0).</li>
<li>Replace the buzzer with a <strong><a href="https://www.teachmemicro.com/arduino-relay-module-tutorial/">relay</a> and a siren</strong> for stronger sound alerts</li>
<li>Create a <strong>map visualization</strong> using <a href="https://www.teachmemicro.com/esp32-gps-google-maps/">Google Maps API</a> for stored quakes.</li>
</ul>
<hr />
<h3>Final Thoughts</h3>
<p>With the NTP-based auto date update, GPS-based distance calculation, SD card logging, RGB alert LED, and now this web dashboard, your ESP32 Earthquake Monitor is a full-fledged, connected device worthy of a spot on your desk.</p>
<p>It not only helps answer “Was that an earthquake?” —it lets you prove it with data, logs, and a neat little web interface.</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-earthquake-monitor-with-usgs-api-gps-lcd-and-web-dashboard/">ESP32 Earthquake Monitor with USGS API, GPS, LCD, and Web Dashboard</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ESP32 Meets AI: Talking to Large Language Models via OpenRouter</title>
		<link>https://www.teachmemicro.com/esp32-meets-ai-talking-to-large-language-models-via-openrouter/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=esp32-meets-ai-talking-to-large-language-models-via-openrouter</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 15 Sep 2025 02:33:34 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=9213</guid>

					<description><![CDATA[<p>Large Language Models (LLMs) like ChatGPT are usually something you access from a laptop or phone. But what if your humble ESP32 could send a question over Wi-Fi and get an answer back? That’s what we’ll build in this tutorial. We’ll make the ESP32 query an LLM through the OpenRouter API, and print the response on &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-meets-ai-talking-to-large-language-models-via-openrouter/">ESP32 Meets AI: Talking to Large Language Models via OpenRouter</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Large Language Models (LLMs) like ChatGPT are usually something you access from a laptop or phone. But what if your humble <strong>ESP32</strong> could send a question over Wi-Fi and get an answer back? That’s what we’ll build in this tutorial. We’ll make the ESP32 query an LLM through the <strong>OpenRouter API</strong>, and print the response on Serial. We’ll cover <strong>two approaches</strong>:</p>
<ol>
<li><strong>Direct:</strong> ESP32 → OpenRouter API</li>
<li><strong>With Proxy:</strong> ESP32 → Python Flask Server → OpenRouter API</li>
</ol>
<p>By the end, you’ll have an ESP32 that can “talk” to AI — and you’ll understand which setup is best for demos vs real projects.</p>
<hr />
<h3><strong>What is OpenRouter?</strong></h3>
<p><a class="decorated-link cursor-pointer" href="https://openrouter.ai/" target="_new" rel="noopener">OpenRouter</a> is a service that lets you use many different AI models (OpenAI, Anthropic, Mistral, LLaMA, etc.) with just <strong>one API</strong>. Instead of juggling keys and endpoints, you use a single <strong>chat completions</strong> endpoint:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">POST https://openrouter.ai/api/v1/chat/completions</code></pre></pre>
</div>
</div>
</div>
</div>
<div class="overflow-y-auto p-4" dir="ltr">You send:</div>
</div>
<ul>
<li><em>Authorization: Bearer &lt;API_KEY&gt;</em></li>
<li>Optional: <em>HTTP-Referer</em> and <em>X-Title</em> headers (to identify your app)</li>
<li>JSON body with <em>model</em>, <em>messages</em>, etc.</li>
</ul>
<p>The response looks like OpenAI’s API: a JSON with <em>choices[0].message.content</em>.</p>
<hr />
<h3><strong>Approach A: ESP32 Directly to OpenRouter</strong></h3>
<p>This is the “bare minimum” setup: the ESP32 connects to Wi-Fi and makes an HTTPS POST request.</p>
<h4><strong>Hardware Needed</strong></h4>
<ul>
<li>Any ESP32 development board</li>
<li>Wi-Fi connection</li>
<li>Arduino IDE</li>
</ul>
<h4><strong>Sketch</strong></h4>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-cpp" data-lang="C++"><pre><code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;WiFiClientSecure.h&gt;
#include &lt;HTTPClient.h&gt;

// ====== EDIT THESE ======
const char* WIFI_SSID = &quot;YOUR_WIFI&quot;;
const char* WIFI_PASS = &quot;YOUR_PASS&quot;;
const char* OPENROUTER_API_KEY = &quot;YOUR_API_KEY&quot;;
const char* OPENROUTER_URL = &quot;https://openrouter.ai/api/v1/chat/completions&quot;;
const char* MODEL = &quot;openai/gpt-4o-mini&quot;;
// ========================

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print(&quot;.&quot;); }
  Serial.println(&quot;\nWiFi connected.&quot;);

  WiFiClientSecure client;
  client.setInsecure(); // For demo only! Use setCACert() in production.

  HTTPClient https;
  if (!https.begin(client, OPENROUTER_URL)) {
    Serial.println(&quot;HTTPS begin failed&quot;);
    return;
  }

  https.addHeader(&quot;Content-Type&quot;, &quot;application/json&quot;);
  https.addHeader(&quot;Authorization&quot;, String(&quot;Bearer &quot;) + OPENROUTER_API_KEY);
  https.addHeader(&quot;HTTP-Referer&quot;, &quot;https://teachmemicro.com&quot;);
  https.addHeader(&quot;X-Title&quot;, &quot;ESP32 LLM Demo&quot;);

  String body = String(&quot;{\&quot;model\&quot;:\&quot;&quot;) + MODEL + &quot;\&quot;,&quot;
                &quot;\&quot;messages\&quot;:[&quot;
                  &quot;{\&quot;role\&quot;:\&quot;system\&quot;,\&quot;content\&quot;:\&quot;Answer in &lt;=25 words.\&quot;},&quot; &quot;{\&quot;role\&quot;:\&quot;user\&quot;,\&quot;content\&quot;:\&quot;Say hi from an ESP32.\&quot;}&quot; &quot;],&quot; &quot;\&quot;max_tokens\&quot;: 50,&quot; &quot;\&quot;temperature\&quot;: 0.7&quot; &quot;}&quot;; int code = https.POST(body); if (code &gt; 0) {
    Serial.printf(&quot;HTTP %d\n&quot;, code);
    String resp = https.getString();
    Serial.println(resp);
  } else {
    Serial.printf(&quot;HTTP error: %d\n&quot;, code);
  }
  https.end();
}

void loop() {}</code></pre></pre>
</div>
</div>
</div>
</div>
</div>
<h4><strong>Things to Note</strong></h4>
<ul>
<li><strong>API key is on the device.</strong> Anyone who gets your ESP32 could extract it.</li>
<li><strong>TLS certificates.</strong> I used <em>setInsecure()</em> for demo. Properly, you should embed OpenRouter’s root CA.</li>
<li><strong>Memory.</strong> ESP32 RAM is small. Don’t ask for long essays; keep <em>max_tokens</em> low.</li>
<li><strong>Parsing JSON.</strong> The full response is big. Use <em>ArduinoJson</em> filters if you only need <em>choices[0].message.content</em>.</li>
</ul>
<p>This works fine for <strong>demos and lab projects</strong>. But for real-world apps, we need more control.</p>
<hr />
<h3><strong>Approach B: ESP32 → Python Proxy → OpenRouter</strong></h3>
<p>Here, the ESP32 doesn’t talk to OpenRouter directly. Instead, it calls a <strong>tiny Python Flask server</strong> you run on your PC, Raspberry Pi, or VPS. The proxy calls OpenRouter, trims the answer, and returns a small JSON.</p>
<p>Why bother?</p>
<ul>
<li>No API key on the ESP32.</li>
<li>Responses are small and easy to parse.</li>
<li>You can switch models or preprocess answers without touching the ESP32.</li>
</ul>
<h3><strong>Setting Up the Python Flask Proxy on Windows</strong></h3>
<p>We’ll create a tiny Flask server that your ESP32 can call on your home network. These steps use <strong>Windows 10/11</strong>, <strong>PowerShell</strong>, and <strong>Python 3.10+</strong>.</p>
<h4><strong>1) Install Python (and add to PATH)</strong></h4>
<ol>
<li>Download Python from <strong>python.org → Downloads → Windows</strong>.</li>
<li>Run the installer and <strong>tick “Add python.exe to PATH.”</strong></li>
<li>Verify in PowerShell:
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">python --version
pip --version</code></pre></pre>
</div>
</div>
</div>
<p>If either command is not found, sign out/in or reboot.</li>
</ol>
<hr />
<h4><strong>2) Make a project folder</strong></h4>
<p>Open <strong>PowerShell</strong> and create a folder anywhere (e.g., Documents):</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">cd $HOME\Documents
mkdir esp32-llm-proxy
cd esp32-llm-proxy</code></pre></pre>
</div>
<hr />
<h4><strong>3) Create and activate a virtual environment</strong></h4>
<p>A <em>venv</em> keeps your dependencies clean and local to the project.</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">python -m venv .venv
.\.venv\Scripts\Activate.ps1   # You should see (.venv) prefix in the prompt</code></pre></pre>
</div>
</div>
</div>
<p>If you get an execution policy error, run PowerShell <strong>as Administrator</strong> once:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">Set-ExecutionPolicy RemoteSigned</code></pre></pre>
</div>
<p>Then retry <em>Activate.ps1</em>.</p>
<h4><strong>4) Add the three files</strong></h4>
<p>Create <strong>requirements.txt</strong>, <strong>.env</strong>, and <strong>server.py</strong> in the folder.</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">esp32-llm-proxy/
├─ server.py
├─ requirements.txt
├─ .env</code></pre></pre>
</div>
</div>
</div>
<p><strong>requirements.txt</strong></p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">flask
requests
python-dotenv</code></pre></pre>
</div>
<p><strong>.env</strong></p>
</div>
</div>
</div>
</div>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">OPENROUTER_API_KEY=your_key_here
MODEL=openai/gpt-4o-mini
APP_URL=https://teachmemicro.com
APP_TITLE=ESP32 LLM Proxy</code></pre></pre>
</div>
</div>
</div>
<p><strong>server.py</strong></p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-python" data-lang="Python"><pre><code class="language-cpp">import os, json, requests
from flask import Flask, request, jsonify
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv(&quot;OPENROUTER_API_KEY&quot;)
MODEL = os.getenv(&quot;MODEL&quot;, &quot;openai/gpt-4o-mini&quot;)
APP_URL = os.getenv(&quot;APP_URL&quot;)
APP_TITLE = os.getenv(&quot;APP_TITLE&quot;)

OPENROUTER_URL = &quot;https://openrouter.ai/api/v1/chat/completions&quot;
app = Flask(__name__)

@app.post(&quot;/ask&quot;)
def ask():
    q = str((request.json or {}).get(&quot;q&quot;, &quot;&quot;))[:200]
    payload = {
        &quot;model&quot;: MODEL,
        &quot;messages&quot;: [
            {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;Answer concisely (&lt;=25 words).&quot;},
            {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: q}
        ],
        &quot;max_tokens&quot;: 60
    }
    headers = {
        &quot;Authorization&quot;: f&quot;Bearer {API_KEY}&quot;,
        &quot;Content-Type&quot;: &quot;application/json&quot;,
        &quot;HTTP-Referer&quot;: APP_URL,
        &quot;X-Title&quot;: APP_TITLE,
    }
    r = requests.post(OPENROUTER_URL, headers=headers, data=json.dumps(payload))
    data = r.json()
    text = data.get(&quot;choices&quot;, [{}])[0].get(&quot;message&quot;, {}).get(&quot;content&quot;, &quot;&quot;)
    return jsonify({&quot;text&quot;: text[:512]})</code></pre></pre>
</div>
</div>
</div>
<div class="overflow-y-auto p-4" dir="ltr">
<p>Run it:</p>
</div>
</div>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">pip install -r requirements.txt
python server.py</code></pre></pre>
</div>
</div>
</div>
</div>
</div>
<p>Test from your PC:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">curl -X POST http://localhost:3000/ask -H &quot;Content-Type: application/json&quot; -d &#039;{&quot;q&quot;:&quot;Say hi!&quot;}&#039;</code></pre></pre>
</div>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<p>Or if you don't have curl:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">Invoke-RestMethod -Method Post -Uri http://localhost:3000/ask -Body (@{q=&quot;Say hi!&quot;} | ConvertTo-Json) -ContentType &quot;application/json&quot;</code></pre></pre>
</div>
<p>You should see a JSON response:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">{&quot;text&quot;:&quot;Hi from the LLM!&quot;}</code></pre></pre>
</div>
<p><strong>ESP32 Sketch (Proxy Version)</strong></p>
<p>Now that your python proxy server is running on PC, upload this sketch to your ESP32:</p>
</div>
</div>
</div>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-cpp" data-lang="C++"><pre><code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;HTTPClient.h&gt; 

const char* WIFI_SSID = &quot;YOUR_WIFI&quot;;
const char* WIFI_PASS = &quot;YOUR_PASS&quot;;
const char* PROXY_URL = &quot;http://192.168.1.100:3000/ask&quot;;

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print(&quot;.&quot;); }
  Serial.println(&quot;\nWiFi connected.&quot;);

  HTTPClient http;
  http.begin(PROXY_URL);
  http.addHeader(&quot;Content-Type&quot;, &quot;application/json&quot;);

  String body = R&quot;({&quot;q&quot;:&quot;ESP32 says hello! Keep it short.&quot;})&quot;;
  int code = http.POST(body);

  if (code &gt; 0) {
    String resp = http.getString(); // Example: {&quot;text&quot;:&quot;Hi from the LLM!&quot;}
    Serial.println(resp);
  } else {
    Serial.printf(&quot;HTTP error: %d\n&quot;, code);
  }
  http.end();
}

void loop() {}</code></pre></pre>
</div>
</div>
</div>
<p>Open <strong>Serial Monitor @ 115200</strong>. You should see the tiny JSON:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">{&quot;text&quot;:&quot;Hi from the LLM!&quot;}</code></pre></pre>
</div>
<hr />
<h3><strong>Pros and Cons</strong></h3>
<p><strong>Direct (ESP32 → OpenRouter)</strong><br />
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Simple, no server<br />
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> One less hop<br />
✘ API key in firmware<br />
✘ Large JSON responses<br />
✘ Hard to change models without reflashing</p>
<p><strong>Proxy (ESP32 → Flask → OpenRouter)</strong><br />
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> API key stays safe<br />
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Tiny, clean responses<br />
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Easy to swap models or add features (caching, logging, RAG)<br />
✘ Requires hosting a server<br />
✘ One extra hop</p>
<hr />
<h3><strong>When to Use Which</strong></h3>
<ul>
<li><strong>For a classroom demo or quick experiment:</strong> Direct is fine.</li>
<li><strong>For anything serious (multiple devices, production, or publishing a project):</strong> Use a proxy.</li>
</ul>
<hr />
<h3><strong>Final Thoughts</strong></h3>
<p>This project shows how the ESP32 — a microcontroller with just a few hundred KB of RAM — can still talk to cutting-edge AI models. Thanks to <strong>OpenRouter</strong>, you can choose models freely without rewriting code.</p>
<p>Start with the direct approach if you just want to see “Hello from ESP32” echoed back by an AI. But if you plan to make an AI gadget (like a smart display or voice assistant), invest the extra step: build a proxy. It makes your setup more secure, scalable, and flexible.</p>
<p>The post <a href="https://www.teachmemicro.com/esp32-meets-ai-talking-to-large-language-models-via-openrouter/">ESP32 Meets AI: Talking to Large Language Models via OpenRouter</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Getting Started with TensorFlow Lite on ESP32 (With Voice Activity Detection Project)</title>
		<link>https://www.teachmemicro.com/getting-started-with-tensorflow-lite-on-esp32-with-voice-activity-detection-project/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=getting-started-with-tensorflow-lite-on-esp32-with-voice-activity-detection-project</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Mon, 16 Jun 2025 23:00:16 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=8683</guid>

					<description><![CDATA[<p>Machine learning (ML) isn’t just for powerful computers anymore. With TensorFlow Lite for Microcontrollers (TFLM), we can run tiny ML models on low-power devices like the ESP32, enabling real-time intelligence at the edge—no cloud needed! What is TensorFlow Lite for Microcontrollers? TensorFlow Lite for Microcontrollers is a lightweight version of TensorFlow designed specifically to run &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/getting-started-with-tensorflow-lite-on-esp32-with-voice-activity-detection-project/">Getting Started with TensorFlow Lite on ESP32 (With Voice Activity Detection Project)</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><a href="https://www.teachmemicro.com/tinyml-with-esp32-tutorial/">Machine learning (ML)</a> isn’t just for powerful computers anymore. With <strong>TensorFlow Lite for Microcontrollers (TFLM)</strong>, we can run tiny ML models on low-power devices like the ESP32, enabling real-time intelligence at the edge—no cloud needed!</p>
<p><span id="more-8683"></span></p>
<h2>What is TensorFlow Lite for Microcontrollers?</h2>
<p>TensorFlow Lite for Microcontrollers is a lightweight version of TensorFlow designed specifically to run machine learning models on devices with limited memory and processing power, such as the ESP32.</p>
<p><strong>Key features include:</strong></p>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Lightweight and optimized for microcontrollers</li>
<li>Can run with less than 100 KB RAM</li>
<li>Supports common ML operations like fully connected layers and convolution</li>
</ul>
</li>
</ul>
<h2>Requirements</h2>
<h3>Hardware</h3>
<ul>
<li style="list-style-type: none;">
<ul>
<li>ESP32 Development Board</li>
<li>Microphone Module (e.g., INMP441, MAX9814)</li>
<li>Micro USB Cable</li>
<li>(Optional) LED</li>
</ul>
</li>
</ul>
<h3>Software</h3>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Arduino IDE</li>
<li>ESP32 Board Package</li>
<li>TensorFlow Lite Micro Arduino Library</li>
<li>Python (optional, for custom model preparation)</li>
</ul>
</li>
</ul>
<h2>Setting Up the Environment</h2>
<h3>1. Install Arduino IDE</h3>
<p style="padding-left: 40px;"><a class="cursor-pointer" href="https://www.arduino.cc/en/software" target="_new" rel="noopener">Download from Arduino.cc</a></p>
<h3>2. Install ESP32 Board Support</h3>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Go to <strong>File → Preferences</strong>, and add the URL: <em>https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json</em></li>
<li>Install via <strong>Tools → Board → Board Manager → ESP32</strong>.</li>
</ul>
</li>
</ul>
<h3>3. Install TensorFlow Lite Library</h3>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Navigate to <strong>Sketch → Include Library → Manage Libraries</strong>.</li>
<li>Search and install the <em>Arduino_TensorFlowLite</em> library.</li>
</ul>
</li>
</ul>
<h2>Practical Example: Voice Activity Detection (VAD)</h2>
<p>This project demonstrates detecting voice activity using TensorFlow Lite on ESP32.</p>
<h3>Step 1: Download and Prepare the Model</h3>
<p>Download the pre-trained model: <a class="" href="https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/examples/micro_speech/models/audio_preprocessor_int8.tflite" target="_new" rel="noopener">Download Model (audio_preprocessor.tflite)</a></p>
<p>Convert the downloaded model into a C source file (<em>speech_model_data.cc</em>) using the following command:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="sticky top-9">
<div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">
<div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">xxd -i audio_prepocessor.tflite &gt; speech_model_data.cc</code></pre></pre>
</div>
</div>
</div>
</div>
<div class="overflow-y-auto p-4" dir="ltr">
<p>You can install Windows Subsystem for Linux using Administrator Command Prompt to run <em>xxd</em>:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-bash" data-lang="Bash"><pre><code class="language-cpp">wsl --install</code></pre></pre>
</div>
<p>Or you can use this python script:</p>
</div>
</div>
<div dir="ltr">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-python" data-lang="Python"><pre><code class="language-cpp">with open(&quot;audio_preprocessor.tflite&quot;, &quot;rb&quot;) as f:
    data = f.read()

with open(&quot;speech_model_data.cc&quot;, &quot;w&quot;) as f:
    f.write(&quot;const unsigned char micro_speech_model_data[] = {&quot;)
    for i, byte in enumerate(data):
        if i % 12 == 0:
            f.write(&quot;\n  &quot;)
        f.write(f&quot;0x{byte:02x}, &quot;)
    f.write(&quot;\n};\nconst int micro_speech_model_data_len = &quot;)
    f.write(f&quot;{len(data)};\n&quot;)</code></pre></pre>
</div>
</div>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">Include this generated file in your Arduino project folder.</div>
</div>
<h3>Step 2: Arduino Sketch (Main Code)</h3>
<p>Open Arduino IDE and create a new sketch. Copy this code into your Arduino sketch file.</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-cpp" data-lang="C++"><pre><code class="language-cpp">#include &quot;TensorFlowLite.h&quot;
#include &quot;speech_model_data.cc&quot;
#include &quot;tensorflow/lite/micro/micro_interpreter.h&quot;
#include &quot;tensorflow/lite/schema/schema_generated.h&quot;
#include &quot;tensorflow/lite/version.h&quot;

const tflite::Model* model = nullptr;
tflite::MicroInterpreter* interpreter = nullptr;
constexpr int kTensorArenaSize = 10 * 1024;
uint8_t tensor_arena[kTensorArenaSize];

void setup() {  
    Serial.begin(115200);  
    model = tflite::GetModel(speech_model_data);  
    if (model-&gt;version() != TFLITE_SCHEMA_VERSION) {    
        Serial.println(&quot;Model schema mismatch!&quot;);    
        return;  
    }  

    static tflite::MicroMutableOpResolver&lt;5&gt; resolver;  
    resolver.AddFullyConnected();  
    resolver.AddSoftmax();  
    resolver.AddReshape();  
    resolver.AddConv2D();  
    resolver.AddMaxPool2D();  
    static tflite::MicroInterpreter static_interpreter(    
        model, resolver, tensor_arena, kTensorArenaSize);  
    interpreter = &amp;static_interpreter;  

    interpreter-&gt;AllocateTensors();  
    Serial.println(&quot;Model ready.&quot;);
}

void loop() {  
    float dummy_input[49 * 10] = {0};  
    // Simulated data (replace with real audio input)  
    memcpy(interpreter-&gt;input(0)-&gt;data.f, dummy_input, sizeof(dummy_input));  
    TfLiteStatus invoke_status = interpreter-&gt;Invoke();  
    if (invoke_status != kTfLiteOk) {    
        Serial.println(&quot;Invoke failed!&quot;);    
        return;  
    }  

    float* output = interpreter-&gt;output(0)-&gt;data.f;  
    Serial.print(&quot;Speech confidence: &quot;);  
    Serial.println(output[1]);  

    delay(500);
}</code></pre></pre>
</div>
</div>
</div>
<h3>Step 3: Real Audio Input (Optional)</h3>
<p>To incorporate a microphone:</p>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Use I2S to read microphone data (e.g., INMP441).</li>
<li>Convert audio data into a log Mel spectrogram.</li>
<li>Feed real audio input instead of the dummy data.</li>
</ul>
</li>
</ul>
<p>You can use libraries such as <a class="cursor-pointer" href="https://www.arduino.cc/reference/en/libraries/arduinofft/" target="_new" rel="noopener">ArduinoFFT</a> for FFT computation.</p>
<h2>Application Possibilities</h2>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Smart Speakers</li>
<li>Voice-controlled Devices</li>
<li>Noise-level Monitoring Systems</li>
</ul>
</li>
</ul>
<h2>Next Steps and Further Exploration</h2>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Train custom models easily with <a class="" href="https://edgeimpulse.com/" target="_new" rel="noopener">Edge Impulse</a>.</li>
<li>Implement network capabilities with <a href="https://www.teachmemicro.com/esp32-wifi-manager-dynamic-ssid-password/">ESP32 Wi-Fi</a>.</li>
</ul>
</li>
</ul>
<h2>Helpful Resources</h2>
<ul>
<li style="list-style-type: none;">
<ul>
<li><a class="cursor-pointer" href="https://www.tensorflow.org/lite/microcontrollers" target="_new" rel="noopener">TensorFlow Lite for Microcontrollers</a></li>
<li><a class="" href="https://github.com/espressif/arduino-esp32" target="_new" rel="noopener">ESP32 Arduino Library Documentation</a></li>
<li><a class="cursor-pointer" href="https://studio.edgeimpulse.com/" target="_new" rel="noopener">Edge Impulse (Free ML training platform)</a></li>
</ul>
</li>
</ul>
<p>The post <a href="https://www.teachmemicro.com/getting-started-with-tensorflow-lite-on-esp32-with-voice-activity-detection-project/">Getting Started with TensorFlow Lite on ESP32 (With Voice Activity Detection Project)</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Building a GPS-Based Weather Station with ESP32, NEO-6M &#038; ST7735 LCD</title>
		<link>https://www.teachmemicro.com/building-a-gps-based-weather-station-with-esp32-neo-6m-st7735-lcd/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=building-a-gps-based-weather-station-with-esp32-neo-6m-st7735-lcd</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Tue, 29 Apr 2025 22:23:35 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=8538</guid>

					<description><![CDATA[<p>Today, I’m sharing one of my favorite ESP32 projects — a GPS-powered weather station. It’s perfect if you’ve got an ESP32 lying around, a little 1.8" ST7735 TFT screen, and a NEO-6M GPS module. We’ll use your real-time coordinates to fetch live weather data from the free Open-Meteo API, then display it in full color &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/building-a-gps-based-weather-station-with-esp32-neo-6m-st7735-lcd/">Building a GPS-Based Weather Station with ESP32, NEO-6M &#038; ST7735 LCD</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Today, I’m sharing one of my favorite ESP32 projects — a <strong>GPS-powered weather station</strong>. It’s perfect if you’ve got an ESP32 lying around, a little 1.8" ST7735 TFT screen, and a NEO-6M GPS module. We’ll use your real-time coordinates to fetch live weather data from the free <a href="https://open-meteo.com" target="_blank" rel="noopener">Open-Meteo API</a>, then display it in full color on the LCD. No need to hard-code your city — the GPS does all the work!</p>
<p><span id="more-8538"></span></p>
<p>This is a fun, real-world IoT project that combines a handful of technologies — GPS, Wi-Fi, APIs, JSON parsing, and good old Arduino code.</p>
<hr />
<h3>What You’ll Need</h3>
<h4>Hardware:</h4>
<ul>
<li>ESP32 Dev Board (I used a generic one)</li>
<li>NEO-6M GPS Module</li>
<li>ST7735 1.8" SPI TFT LCD</li>
<li>Breadboard and jumper wires</li>
<li>Power source (USB cable or battery)</li>
</ul>
<h4>Software:</h4>
<ul>
<li>Arduino IDE</li>
<li>Required libraries (we’ll get to that in a sec)</li>
</ul>
<hr />
<h3>Wiring It Up</h3>
<p>Let’s hook up everything. Here’s the connection table:</p>
<table>
<thead>
<tr>
<th>Module</th>
<th>ESP32 Pin</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>NEO-6M GPS</strong></td>
<td></td>
</tr>
<tr>
<td>TX</td>
<td>GPIO 16</td>
</tr>
<tr>
<td>RX</td>
<td>GPIO 17</td>
</tr>
<tr>
<td>VCC</td>
<td>3.3V or 5V</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
</tr>
<tr>
<td><strong>ST7735 LCD</strong></td>
<td></td>
</tr>
<tr>
<td>CS</td>
<td>GPIO 5</td>
</tr>
<tr>
<td>DC</td>
<td>GPIO 2</td>
</tr>
<tr>
<td>RST</td>
<td>GPIO 4</td>
</tr>
<tr>
<td>MOSI</td>
<td>GPIO 23</td>
</tr>
<tr>
<td>SCLK</td>
<td>GPIO 18</td>
</tr>
<tr>
<td>VCC</td>
<td>3.3V</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
</tr>
</tbody>
</table>
<p><em><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4dd.png" alt="📝" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Note: The GPS module works best outdoors or near a window. It needs a GPS fix before it can provide coordinates.</em></p>
<hr />
<h3>The Code</h3>
<p>Here's the full code for this project:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-cpp" data-lang="C++"><pre><code class="language-cpp">&lt;wifi.h&gt;&lt;httpclient.h&gt;&lt;tinygpsplus.h&gt;&lt;hardwareserial.h&gt;&lt;adafruit_gfx.h&gt;&lt;adafruit_st7735.h&gt;&lt;spi.h&gt;#include &lt;WiFi.h&gt;
#include &lt;HTTPClient.h&gt;
#include &lt;TinyGPSPlus.h&gt;
#include &lt;HardwareSerial.h&gt;
#include &lt;Adafruit_GFX.h&gt;
#include &lt;Adafruit_ST7735.h&gt;
#include &lt;SPI.h&gt;
#include &lt;ArduinoJson.h&gt; &lt;arduinojson.h&gt;

// ========== WiFi Credentials ==========
const char* ssid = &quot;YOUR_SSID&quot;;
const char* password = &quot;YOUR_PASSWORD&quot;;

// ========== GPS Setup ==========
HardwareSerial GPSserial(2); // UART2
TinyGPSPlus gps;

// ========== LCD Setup ==========
#define TFT_CS     5
#define TFT_RST    4
#define TFT_DC     2
#define TFT_SCLK  18
#define TFT_MOSI  23

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

// ========== Timing ==========
unsigned long lastUpdate = 0;
const unsigned long updateInterval = 60000; // 60 sec

void setup() {
  Serial.begin(115200);
  GPSserial.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17

  tft.initR(INITR_BLACKTAB);
  tft.fillScreen(ST77XX_BLACK);
  tft.setRotation(1);
  tft.setTextWrap(false);
  tft.setTextColor(ST77XX_WHITE);
  tft.setTextSize(1);
  tft.setCursor(0, 0);
  tft.println(&quot;Connecting to WiFi...&quot;);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    tft.print(&quot;.&quot;);
  }

  tft.println(&quot;\nWiFi connected!&quot;);
}

void loop() {
  // Continuously read GPS data
  while (GPSserial.available() &gt; 0) {
    gps.encode(GPSserial.read());
  }

  // Check if valid GPS data is available
  if (gps.location.isValid() &amp;&amp; millis() - lastUpdate &gt; updateInterval) {
    lastUpdate = millis();
    float latitude = gps.location.lat();
    float longitude = gps.location.lng();

    Serial.printf(&quot;GPS: %.6f, %.6f\n&quot;, latitude, longitude);
    fetchAndDisplayWeather(latitude, longitude);
  }
}

void fetchAndDisplayWeather(float lat, float lon) {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    String url = &quot;https://api.open-meteo.com/v1/forecast?latitude=&quot; + 
                 String(lat, 6) + &quot;&amp;longitude=&quot; + String(lon, 6) +
                 &quot;&amp;current_weather=true&quot;;

    http.begin(url);
    int httpCode = http.GET();

    if (httpCode &gt; 0) {
      String payload = http.getString();
      Serial.println(payload);

      DynamicJsonDocument doc(1024);
      DeserializationError error = deserializeJson(doc, payload);

      if (!error) {
        float temperature = doc[&quot;current_weather&quot;][&quot;temperature&quot;];
        float windspeed = doc[&quot;current_weather&quot;][&quot;windspeed&quot;];
        int weathercode = doc[&quot;current_weather&quot;][&quot;weathercode&quot;];

        tft.fillScreen(ST77XX_BLACK);
        tft.setCursor(0, 0);
        tft.setTextColor(ST77XX_WHITE);
        tft.setTextSize(1);

        tft.printf(&quot;Lat: %.2f\n&quot;, lat);
        tft.printf(&quot;Lon: %.2f\n&quot;, lon);
        tft.printf(&quot;Temp: %.1f C\n&quot;, temperature);
        tft.printf(&quot;Wind: %.1f km/h\n&quot;, windspeed);
        tft.printf(&quot;Code: %d\n&quot;, weathercode);

        displayWeatherIcon(weathercode);
      } else {
        Serial.println(&quot;JSON parse failed!&quot;);
      }
    } else {
      Serial.println(&quot;HTTP request failed.&quot;);
    }

    http.end();
  }
}

void displayWeatherIcon(int code) {
  tft.setTextSize(1);
  tft.setTextColor(ST77XX_CYAN);
  tft.setCursor(0, 70);

  // Simplified icon logic
  if (code == 0) {
    tft.println(&quot;Clear&quot;);
  } else if (code &lt;= 3) {
    tft.println(&quot;Partly Cloudy&quot;);
  } else if (code &lt;= 45) {
    tft.println(&quot;Cloudy&quot;);
  } else if (code &lt;= 67) {
    tft.println(&quot;Rain&quot;);
  } else if (code &lt;= 86) {
    tft.println(&quot;Snow&quot;);
  } else {
    tft.println(&quot;Fog&quot;);
  }
}
&lt;/arduinojson.h&gt;&lt;/spi.h&gt;&lt;/adafruit_st7735.h&gt;&lt;/adafruit_gfx.h&gt;&lt;/hardwareserial.h&gt;&lt;/tinygpsplus.h&gt;&lt;/httpclient.h&gt;&lt;/wifi.h&gt;</code></pre></pre>
</div>
<h4>1. Install Required Libraries</h4>
<p>In Arduino IDE, go to <strong>Library Manager</strong> and install the following:</p>
<ul>
<li><em>TinyGPSPlus</em> by Mikal Hart</li>
<li><em>Adafruit GFX</em> and <em>Adafruit ST7735</em></li>
<li><em>ArduinoJson</em> by Benoit Blanchon</li>
<li><em>HTTPClient</em> (included with ESP32)</li>
</ul>
<h4>2. Setup Wi-Fi</h4>
<p>Replace this with your actual credentials:</p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-cpp" data-lang="C++"><pre><code class="language-cpp">const char* ssid = &quot;YOUR_SSID&quot;;
const char* password = &quot;YOUR_PASSWORD&quot;;</code></pre></pre>
</div>
<h4>3. Upload and Run!</h4>
<p>Upload the sketch to your ESP32. Open the Serial Monitor at 115200 baud, and give it a minute or two to get GPS data.</p>
<hr />
<h3>What You’ll See on the LCD</h3>
<ul>
<li>Your <strong>real-time GPS coordinates</strong></li>
<li>Current <strong>temperature</strong></li>
<li><strong>Wind speed</strong></li>
<li>A simple <strong>weather message</strong> (Clear, Rainy, etc.)</li>
</ul>
<p>All this on a colorful, 1.8" TFT screen! Perfect for a portable gadget or even mounting to a bike or backpack.</p>
<hr />
<h3>How It Works</h3>
<ol>
<li><strong>GPS Module</strong> gives real-time latitude and longitude.</li>
<li><strong>ESP32</strong> connects to Wi-Fi.</li>
<li>It builds a request to the <strong>Open-Meteo API</strong> using your location.</li>
<li>Parses the JSON response using <pre><code class="language-cpp">ArduinoJson</code></pre>.</li>
<li>Displays the info on the <strong>ST7735 LCD</strong>.</li>
</ol>
<hr />
<h3>Why Use GPS?</h3>
<p>Because it makes this project <strong>location-agnostic</strong> — you can carry the device anywhere, and it'll always show the weather for your <strong>exact location</strong>.</p>
<p>No need to enter a city, zip code, or country. Let the satellites handle it <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f604.png" alt="😄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<hr />
<h3>Possible Upgrades</h3>
<ul>
<li>Add a <strong>battery and enclosure</strong> to make it portable.</li>
<li>Add <strong>alerts or buzzers</strong> for extreme weather.</li>
<li>Store weather logs on an <strong>SD card</strong>.</li>
<li>Switch to <strong>ePaper</strong> if you want ultra-low power.</li>
</ul>
<hr />
<h3>Final Thoughts</h3>
<p>This project is a great intro to integrating hardware with cloud APIs. I loved seeing live GPS data triggering real-time weather updates. It’s magical how much you can do with just a few modules!</p>
<p>If you build this, I’d love to hear about it. Got stuck somewhere? Just ask!</p>
<p>The post <a href="https://www.teachmemicro.com/building-a-gps-based-weather-station-with-esp32-neo-6m-st7735-lcd/">Building a GPS-Based Weather Station with ESP32, NEO-6M &#038; ST7735 LCD</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Interfacing MLX90614 Non-Contact Infrared Temperature Sensor with ESP32</title>
		<link>https://www.teachmemicro.com/interfacing-mlx90614-non-contact-infrared-temperature-sensor-with-esp32/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=interfacing-mlx90614-non-contact-infrared-temperature-sensor-with-esp32</link>
		
		<dc:creator><![CDATA[Roland Pelayo]]></dc:creator>
		<pubDate>Fri, 21 Mar 2025 21:20:27 +0000</pubDate>
				<category><![CDATA[ESP32 Projects]]></category>
		<category><![CDATA[Sensor Tutorial]]></category>
		<guid isPermaLink="false">https://www.teachmemicro.com/?p=8355</guid>

					<description><![CDATA[<p>Introduction Ever wanted to measure temperature without actually touching anything? Meet the MLX90614, a cool little infrared sensor that lets you do just that! Whether you're building a DIY thermometer, automating your home, or monitoring industrial equipment, this sensor has got you covered. We have already covered how to use the MLX90614 with an Arduino. &#8230;</p>
<p>The post <a href="https://www.teachmemicro.com/interfacing-mlx90614-non-contact-infrared-temperature-sensor-with-esp32/">Interfacing MLX90614 Non-Contact Infrared Temperature Sensor with ESP32</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h3><b>Introduction</b></h3>
<p><span style="font-weight: 400;">Ever wanted to measure temperature without actually touching anything? Meet the </span><b>MLX90614</b><span style="font-weight: 400;">, a cool little infrared sensor that lets you do just that! Whether you're building a DIY thermometer, automating your home, or monitoring industrial equipment, this sensor has got you covered. We have already covered how to use the MLX90614 with an Arduino. In this guide, we’ll hook up the MLX90614 to an </span><b>ESP32</b><span style="font-weight: 400;">, walk through the wiring, dive into the code, and throw in some handy troubleshooting tips along the way.</span></p>
<h3><b>MLX90614 Specifications</b></h3>
<ul>
<li style="font-weight: 400;" aria-level="1"><b>Wide Temperature Range</b><span style="font-weight: 400;">: -70°C to 380°C</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Super Accurate</b><span style="font-weight: 400;">: ±0.5°C (in the 0°C to 50°C range)</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Tiny Details Matter</b><span style="font-weight: 400;">: 0.02°C resolution</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Uses I2C for Easy Communication</b></li>
<li style="font-weight: 400;" aria-level="1"><b>Plays Well with 3.3V and 5V Devices</b></li>
<li style="font-weight: 400;" aria-level="1"><b>Decent Field of View (FoV)</b><span style="font-weight: 400;">: Around 35°</span></li>
</ul>
<h3><b>Materials</b></h3>
<ul>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">An </span><b>ESP32</b><span style="font-weight: 400;"> board</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">An </span><b>MLX90614</b><span style="font-weight: 400;"> infrared temperature sensor</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">A bunch of </span><b>jumper wires</b></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">A </span><b>breadboard</b><span style="font-weight: 400;"> (optional but makes life easier)</span></li>
</ul>
<h3><b>Wiring Diagram</b></h3>
<p><span style="font-weight: 400;">Connecting the MLX90614 to the ESP32 is a breeze. Just match up these pins:</span></p>
<table>
<tbody>
<tr>
<td><b>MLX90614 Pin</b></td>
<td><b>ESP32 Pin</b></td>
</tr>
<tr>
<td><span style="font-weight: 400;">VCC</span></td>
<td><span style="font-weight: 400;">3.3V</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">GND</span></td>
<td><span style="font-weight: 400;">GND</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">SDA</span></td>
<td><span style="font-weight: 400;">GPIO21</span></td>
</tr>
<tr>
<td><span style="font-weight: 400;">SCL</span></td>
<td><span style="font-weight: 400;">GPIO22</span></td>
</tr>
</tbody>
</table>
<h3><b>Required Libraries</b></h3>
<p><span style="font-weight: 400;">Before jumping into the code, let's grab some libraries in the Arduino IDE:</span></p>
<ol>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Open </span><b>Arduino IDE</b><span style="font-weight: 400;">.</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Go to </span><b>Sketch</b><span style="font-weight: 400;"> &gt; </span><b>Include Library</b><span style="font-weight: 400;"> &gt; </span><b>Manage Libraries</b><span style="font-weight: 400;">.</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Search for </span><b>Adafruit MLX90614</b><span style="font-weight: 400;"> and hit install.</span></li>
<li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">If needed, install </span><b>Adafruit BusIO</b><span style="font-weight: 400;"> as well.</span></li>
</ol>
<h3><b>Reading Temperature</b></h3>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-cpp" data-lang="C++"><pre><code class="language-cpp">#include &lt;Wire.h&gt;
#include &lt;Adafruit_MLX90614.h&gt;


Adafruit_MLX90614 mlx = Adafruit_MLX90614();

void setup() {
    Serial.begin(115200);
    Serial.println(&quot;MLX90614 Sensor Check...&quot;);
    if (!mlx.begin()) {
        Serial.println(&quot;Uh-oh! Can&#039;t find the MLX90614. Check your wiring!&quot;);
        while (1);
    }
}


void loop() {
    Serial.print(&quot;Ambient Temp: &quot;);
    Serial.print(mlx.readAmbientTempC());
    Serial.println(&quot; °C&quot;);
    Serial.print(&quot;Object Temp: &quot;);
    Serial.print(mlx.readObjectTempC());
    Serial.println(&quot; °C&quot;); 

    delay(1000);
}</code></pre></pre>
</div>
<h4><b>Code Explanation:</b></h4>
<ol>
<li style="font-weight: 400;" aria-level="1"><b>Library Setup</b><span style="font-weight: 400;">: We include </span><span style="font-weight: 400;">Wire.h</span><span style="font-weight: 400;"> for I2C and </span><span style="font-weight: 400;">Adafruit_MLX90614.h</span><span style="font-weight: 400;"> for sensor functions.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Sensor Check</b><span style="font-weight: 400;">: </span><span style="font-weight: 400;">mlx.begin()</span><span style="font-weight: 400;"> ensures we can communicate with the MLX90614.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Reading Temperatures</b><span style="font-weight: 400;">:</span>
<ul>
<li style="font-weight: 400;" aria-level="2"><span style="font-weight: 400;">mlx.readAmbientTempC()</span><span style="font-weight: 400;"> gives the surrounding temperature.</span></li>
<li style="font-weight: 400;" aria-level="2"><span style="font-weight: 400;">mlx.readObjectTempC()</span><span style="font-weight: 400;"> gets the temperature of whatever it’s pointed at.</span></li>
</ul>
</li>
<li style="font-weight: 400;" aria-level="1"><b>Serial Monitor Fun</b><span style="font-weight: 400;">: Readings are printed every second for easy debugging.</span></li>
</ol>
<p>&nbsp;</p>
<h3><b>Creating a Web Server to Display Temperature Readings</b></h3>
<p><span style="font-weight: 400;">If you want to view the temperature readings from any device on your network, let's create a simple web server on the ESP32.</span></p>
<div class="hcb_wrap">
<pre class="prism undefined-numbers lang-cpp" data-lang="C++"><pre><code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;Wire.h&gt;
#include &lt;Adafruit_MLX90614.h&gt;
#include &lt;ESPAsyncWebServer.h&gt;

const char* ssid = &quot;your_SSID&quot;;
const char* password = &quot;your_PASSWORD&quot;;

Adafruit_MLX90614 mlx = Adafruit_MLX90614();
AsyncWebServer server(80);

void setup() {
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println(&quot;Connecting to WiFi...&quot;);
    }

    Serial.println(&quot;Connected to WiFi&quot;);


    if (!mlx.begin()) {
        Serial.println(&quot;Failed to find MLX90614 sensor!&quot;);
        while (1);
    }


    server.on(&quot;/temperature&quot;, HTTP_GET, [](AsyncWebServerRequest *request) {
        String response = &quot;Ambient Temp: &quot; + String(mlx.readAmbientTempC()) + &quot; °C\n&quot;;
        response += &quot;Object Temp: &quot; + String(mlx.readObjectTempC()) + &quot; °C&quot;;
        request-&gt;send(200, &quot;text/plain&quot;, response);
    });

    server.begin();
}


void loop() {
    // Nothing needed here, the server handles everything!
}</code></pre></pre>
</div>
<h4><b>Code Explanation:</b></h4>
<ol>
<li style="font-weight: 400;" aria-level="1"><b>WiFi Setup</b><span style="font-weight: 400;">: Connects the ESP32 to your WiFi network.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Web Server Setup</b><span style="font-weight: 400;">: Creates a simple web server that responds to requests.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Temperature Data on Request</b><span style="font-weight: 400;">: Visiting </span><span style="font-weight: 400;">http://your-esp32-ip/temperature</span><span style="font-weight: 400;"> will display the temperature readings.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>No Looping Needed</b><span style="font-weight: 400;">: Since the web server runs in the background, there's no need for code in </span><span style="font-weight: 400;">loop()</span><span style="font-weight: 400;">.</span></li>
</ol>
<h3><b>Tips for More Accurate Readings</b></h3>
<ul>
<li style="font-weight: 400;" aria-level="1"><b>Know Your Emissivity</b><span style="font-weight: 400;">: Most organic materials work well with the default 0.95 setting, but metals or shiny surfaces may need adjustments.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Keep the Right Distance</b><span style="font-weight: 400;">: Too close or too far can affect accuracy.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Avoid Direct Heat Sources</b><span style="font-weight: 400;">: Bright lights or strong infrared sources can throw off readings.</span></li>
</ul>
<h3><b>What If Things Go Wrong?</b></h3>
<ul>
<li style="font-weight: 400;" aria-level="1"><b>No readings?</b><span style="font-weight: 400;"> Double-check your wiring, especially SDA/SCL connections.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Weird errors?</b><span style="font-weight: 400;"> Use an </span><b>I2C scanner script</b><span style="font-weight: 400;"> to verify the sensor’s address.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Inconsistent numbers?</b><span style="font-weight: 400;"> Let the sensor stabilize before trusting the values.</span></li>
</ul>
<h3><b>Project Ideas for the MLX90614</b></h3>
<ul>
<li style="font-weight: 400;" aria-level="1"><b>DIY Contactless Thermometer</b><span style="font-weight: 400;">: Perfect for checking object or body temperatures.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Smart Home Integration</b><span style="font-weight: 400;">: Use temperature readings to trigger fans, AC, or heating.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Machine Health Monitoring</b><span style="font-weight: 400;">: Detect overheating components in real-time.</span></li>
<li style="font-weight: 400;" aria-level="1"><b>Greenhouse or Animal Monitoring</b><span style="font-weight: 400;">: Keep track of temperature conditions easily.</span></li>
</ul>
<h3><b>Handy Resources</b></h3>
<ul>
<li style="font-weight: 400;" aria-level="1"><a href="https://www.melexis.com/-/media/files/documents/datasheets/mlx90614-datasheet-melexis.pdf"><span style="font-weight: 400;">MLX90614 Datasheet</span></a></li>
<li style="font-weight: 400;" aria-level="1"><a href="https://github.com/adafruit/Adafruit-MLX90614-Library"><span style="font-weight: 400;">Adafruit MLX90614 Library</span></a></li>
<li style="font-weight: 400;" aria-level="1"><a href="https://playground.arduino.cc/Main/I2cScanner/"><span style="font-weight: 400;">I2C Scanner Code</span></a></li>
</ul>
<h3><b>Wrapping It Up</b></h3>
<p><span style="font-weight: 400;">And there you have it! The </span><b>MLX90614 + ESP32</b><span style="font-weight: 400;"> combo is a powerful tool for measuring temperatures without contact. Whether you’re making a thermometer or automating home gadgets, this sensor is a great addition to your projects. Now, with the added web server, you can monitor temperatures remotely! So go ahead, start experimenting, and have fun coding!</span></p>
<p>The post <a href="https://www.teachmemicro.com/interfacing-mlx90614-non-contact-infrared-temperature-sensor-with-esp32/">Interfacing MLX90614 Non-Contact Infrared Temperature Sensor with ESP32</a> appeared first on <a href="https://www.teachmemicro.com">Microcontroller Tutorials</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: www.teachmemicro.com @ 2026-07-22 20:56:32 by W3 Total Cache
-->