<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TheTechLover</title>
    <description>The latest articles on DEV Community by TheTechLover (@thetechlover).</description>
    <link>https://dev.to/thetechlover</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4231342%2Fb14d3689-d840-4629-b752-658a6a7df73a.png</url>
      <title>DEV Community: TheTechLover</title>
      <link>https://dev.to/thetechlover</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thetechlover"/>
    <language>en</language>
    <item>
      <title>Why Your Flow Sensor Lies About Thick Oil (and How to Read the Right Meter with an ESP32)</title>
      <dc:creator>TheTechLover</dc:creator>
      <pubDate>Sat, 07 Nov 2026 03:07:05 +0000</pubDate>
      <link>https://dev.to/thetechlover/why-your-flow-sensor-lies-about-thick-oil-and-how-to-read-the-right-meter-with-an-esp32-1d16</link>
      <guid>https://dev.to/thetechlover/why-your-flow-sensor-lies-about-thick-oil-and-how-to-read-the-right-meter-with-an-esp32-1d16</guid>
      <description>&lt;p&gt;If you have built anything that measures liquid flow with a microcontroller, you have probably used one of those little hall-effect paddle-wheel sensors. You count pulses, divide by a number from the datasheet and get litres per minute. For water, it works nicely.&lt;/p&gt;

&lt;p&gt;Then someone asks you to log gear oil going into a gearbox, hydraulic fluid in a test rig or glycerin in a mixing line. You wire up the same sensor, and the readings drift low, stall at low flow or stop altogether. Nothing is wrong with your code. The physics changed.&lt;/p&gt;

&lt;p&gt;This post covers why viscosity breaks small turbine-style sensors, which kind of meter to use instead, and how to read it properly with an ESP32: interrupts, low-flow handling, temperature and a calibration you can trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why thick fluids fool paddle wheels and turbines
&lt;/h2&gt;

&lt;p&gt;A paddle-wheel or turbine sensor works because moving liquid pushes a rotor round, and the rotor speed follows the flow velocity. Its calibration constant, the K-factor in pulses per litre, assumes the fluid behaves like water: low viscosity, turbulent flow and very little drag on the blades.&lt;/p&gt;

&lt;p&gt;Thick fluids break those assumptions in three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Drag.&lt;/strong&gt; A viscous fluid grips the rotor and its bearings, so the rotor turns slower than the flow suggests, especially at low flow rates. The sensor reads low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laminar flow.&lt;/strong&gt; As viscosity rises, the Reynolds number falls and the flow turns laminar. The velocity profile changes from fairly flat to a parabola, so the blades see a different average velocity than they did during calibration, and the K-factor shifts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature.&lt;/strong&gt; Oil viscosity changes dramatically with temperature. The same sensor can be close on a warm afternoon and several percent off on a cold morning.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Industrial turbine meters deal with this through calibration curves for different viscosities. A hobby sensor has one number on its datasheet, measured with water.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a meter that likes viscosity
&lt;/h2&gt;

&lt;p&gt;For oils, syrups, resins and similar liquids, the usual answer is a positive displacement (PD) meter, most often the oval gear type. Two meshing oval gears trap a fixed volume of liquid on every turn and pass it through. Count the turns and you have counted the volume.&lt;/p&gt;

&lt;p&gt;The main error source in a PD meter is slip: liquid leaking through the small clearances between the gears and the housing. The thicker the fluid, the less it slips, so accuracy often improves as viscosity rises. The trade-off is pressure drop, which also rises with viscosity, which is why manufacturers derate the maximum flow for very thick fluids. Coriolis meters are the other common choice when you need mass flow directly and the budget allows it.&lt;/p&gt;

&lt;p&gt;When you choose hardware, a purpose-built &lt;a href="https://www.silverinstruments.com/high-viscosity-fluid-flow-meters.html" rel="noopener noreferrer"&gt;high viscosity oil flow meter&lt;/a&gt; states its viscosity range, pressure drop and pulse output on the datasheet, and those are exactly the numbers your firmware needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the meter gives your microcontroller
&lt;/h2&gt;

&lt;p&gt;Most meters offer one or more of these outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pulses:&lt;/strong&gt; a reed switch, a hall sensor or an open-collector (NPN) transistor. Each pulse stands for a fixed volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4-20 mA:&lt;/strong&gt; an analog current proportional to flow, read through a shunt resistor and an ADC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modbus RTU&lt;/strong&gt; over RS-485: registers that hold the flow rate and the totals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pulses are the simplest and the most accurate for totals, so that is what we will use.&lt;/p&gt;

&lt;p&gt;A few wiring notes for an ESP32:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An NPN open-collector output can only pull the line low, so add an external pull-up of 4.7 to 10 kΩ to 3.3 V. The internal pull-up is too weak for a long cable.&lt;/li&gt;
&lt;li&gt;A PNP output, or anything that drives 12 or 24 V, needs a voltage divider or, better, an optocoupler. Never connect 24 V to a GPIO.&lt;/li&gt;
&lt;li&gt;Reed switches bounce. Add a small RC filter, for example 10 kΩ and 100 nF, plus a short software lockout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The firmware: count in an interrupt, compute in the loop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Arduino.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Preferences.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;      &lt;span class="n"&gt;PULSE_PIN&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;    &lt;span class="n"&gt;PULSES_PER_LITRE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// K-factor: use the value on your meter's calibration sheet&lt;/span&gt;
&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;MIN_GAP_US&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// ignore edges closer than 2 ms (contact bounce)&lt;/span&gt;

&lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;pulseCount&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;lastPulseUs&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;lastPeriodUs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;portMUX_TYPE&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;portMUX_INITIALIZER_UNLOCKED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;IRAM_ATTR&lt;/span&gt; &lt;span class="nf"&gt;onPulse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;micros&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;portENTER_CRITICAL_ISR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastPulseUs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MIN_GAP_US&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pulseCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;lastPeriodUs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;lastPulseUs&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;portEXIT_CRITICAL_ISR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Preferences&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;totalPulses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PULSE_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                       &lt;span class="c1"&gt;// external pull-up on the board&lt;/span&gt;
  &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;totalPulses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getULong64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// the total survives a reboot&lt;/span&gt;
  &lt;span class="n"&gt;attachInterrupt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digitalPinToInterrupt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PULSE_PIN&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;onPulse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FALLING&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;lastReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastSave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastReport&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastReport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1000.0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;lastReport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;portENTER_CRITICAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;pulses&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pulseCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;pulseCount&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lastPeriodUs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;sinceLast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;micros&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastPulseUs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;portEXIT_CRITICAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;totalPulses&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pulses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;litresPerMin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pulses&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;litresPerMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pulses&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;PULSES_PER_LITRE&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;60.0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// plenty of pulses: count them&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;sinceLast&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10000000UL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;litresPerMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;60.0e6f&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PULSES_PER_LITRE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// slow flow: time between pulses&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;litresPerMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                         &lt;span class="c1"&gt;// no pulse for 10 s: stopped&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;lpm&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:%.3f,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;total_l&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:%.3f}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;litresPerMin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalPulses&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;PULSES_PER_LITRE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;lastSave&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;               &lt;span class="c1"&gt;// save once a minute, not on every pulse&lt;/span&gt;
    &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;putULong64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalPulses&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;lastSave&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details are worth calling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The interrupt only counts.&lt;/strong&gt; On the ESP32 the handler lives in IRAM, and the critical section stops the loop from reading a half-updated count. All the floating-point maths happens in the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two ways to get the rate.&lt;/strong&gt; At normal flow there are plenty of pulses per second, so counting them is accurate. At low flow a viscous fluid may produce only one pulse every few seconds, and counting would make the reading jump between zero and a large number. Timing the gap between pulses gives a smooth value instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose the lockout from the maximum frequency.&lt;/strong&gt; The highest pulse rate is the K-factor times the maximum flow in litres per second. With 100 pulses per litre at 60 L/min, that is 100 Hz, or 10 ms between pulses, so a 2 ms lockout filters bounce without dropping real pulses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save the total sparingly.&lt;/strong&gt; Flash memory wears out with repeated writes, so store the running total once a minute and accept that a power cut may lose a few seconds of counting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Temperature, density and a calibration you can trust
&lt;/h2&gt;

&lt;p&gt;A PD meter measures volume at line conditions, and oil expands as it warms, roughly 0.07 % per °C for many mineral oils. A litre at 60 °C is therefore less oil than a litre at 15 °C. If you dose, sell or report quantities, log the temperature next to the pulses (a DS18B20 strapped to the pipe is enough for monitoring) and convert to a reference temperature, or to mass, with the fluid's density table.&lt;/p&gt;

&lt;p&gt;Then calibrate in place. Dispense into a container on a scale, record the pulses and work out K = pulses / (mass / density). Repeat at low, medium and high flow and at the temperatures you actually run at. If the K values agree within your tolerance, use their average. If they do not, the meter is outside its range or something is slipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the logic before you touch the pipe
&lt;/h2&gt;

&lt;p&gt;You can debug most of this without a drop of oil. A function generator, or a second microcontroller toggling a pin at a known frequency, shows whether the rate, low-flow and total calculations are right. If you prefer simulation, The Engineering Projects publishes a &lt;a href="https://www.theengineeringprojects.com/2026/10/yf-s201-water-flow-sensor-library-for-proteus-v1-0.html" rel="noopener noreferrer"&gt;YF-S201 water flow sensor library for Proteus&lt;/a&gt; that produces the same kind of pulse train, so you can step through the interrupt and loop code on a virtual board first and drop in your real meter's K-factor later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Paddle-wheel sensors are calibrated with water. Viscosity adds drag and changes the flow profile, so they read low and drift with temperature.&lt;/li&gt;
&lt;li&gt;Positive displacement (oval gear) meters suit thick fluids because slip falls as viscosity rises. Size them for pressure drop.&lt;/li&gt;
&lt;li&gt;Count pulses in an interrupt, compute in the loop, switch to pulse timing at low flow and save totals sparingly.&lt;/li&gt;
&lt;li&gt;Log temperature and calibrate by weight under your real operating conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get those right and your dashboard will tell you how much oil actually moved, not how hard it was to push.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>esp32</category>
      <category>embedded</category>
      <category>arduino</category>
    </item>
  </channel>
</rss>
