The YFS201 flow rate sensor outputs exactly 4.5 pulses per second for every Liter per minute (L/min) of water passing through it. To read this with an Arduino, you must connect the yellow signal wire to a hardware interrupt pin (Pin 2 on the Uno R3), use a 10kΩ pull-up resistor to 5V, and measure the pulse frequency over a 1-second window. This guide targets the Arduino Uno R3 (ATmega328P) and provides the exact hardware debounce circuit and non-blocking C++ code required to get stable, noise-free readings on the bench.
Project Specs & Parts List
Estimated Time: 45 minutes
Target Board: Arduino Uno R3 (Rev3) - ATmega328P
Most generic tutorials tell you to wire the sensor directly to the Arduino. In practice, the long cables and switching pumps in water systems generate electromagnetic interference (EMI) that causes "ghost pulses." The parts list below includes the passive components needed to filter this noise.
| Component | Exact Variant / Spec | Estimated Cost | Purpose |
|---|---|---|---|
| Flow Sensor | YFS201 (1/2" NPT Brass) or YF-S401 (1/4" Plastic) | $8.00 - $12.00 | Hall-effect pulse generation |
| Microcontroller | Arduino Uno R3 (ATmega328P) | $22.00 - $27.00 | Interrupt handling and math |
| Pull-up Resistor | 10kΩ (1/4W, 5% tolerance) | $0.10 | Biases signal line HIGH to prevent floating |
| Decoupling Capacitor | 0.1µF (100nF) Ceramic | $0.15 | Filters high-frequency EMI / hardware debounce |
| Wiring | 22 AWG stranded hookup wire | $2.00 | Flexible connections to breadboard |
Pin Mapping & Wiring Steps
The YFS201 has three wires: Red (VCC), Black (GND), and Yellow (Signal). The sensor operates on 5V to 18V, but since the Arduino Uno's ATmega328P GPIO pins are not 5V-tolerant above 5V (and the sensor outputs a 5V square wave when powered at 5V), we power it directly from the Uno's 5V rail.
| Sensor Wire | Arduino Uno R3 Pin | Notes |
|---|---|---|
| Red (VCC) | 5V | Do not use 3.3V; the internal Hall IC requires ~4.5V minimum. |
| Black (GND) | GND | Ensure a common ground with the Arduino. |
| Yellow (Signal) | Digital Pin 2 | Pin 2 maps to Hardware Interrupt 0 (INT0) on the Uno. |
Numbered Wiring Steps with Hardware Debounce
- Power the sensor: Connect the Red wire to the Arduino 5V pin and the Black wire to GND.
- Install the pull-up resistor: Insert a 10kΩ resistor on your breadboard. Connect one leg to the 5V rail and the other leg to the row where the Yellow signal wire will terminate. This ensures the signal line rests at a clean HIGH state when the internal open-collector transistor is off.
- Install the debounce capacitor: Insert a 0.1µF ceramic capacitor. Connect one leg to the Yellow signal wire row and the other leg to GND. This creates a low-pass filter that absorbs nanosecond EMI spikes from nearby pump motors.
- Connect the signal: Run a jumper wire from the Yellow signal row (where the resistor and capacitor meet) to Digital Pin 2 on the Arduino.
- Verify connections: Use a multimeter in continuity mode to verify there are no shorts between 5V and GND before powering on the board.
Compilable Arduino Code with Interrupt Handling
The following code uses attachInterrupt() to count pulses without blocking the main loop. It samples the pulse count exactly every 1000ms using millis(), calculates the flow rate, and includes error handling for sensor overflow or disconnected states. For a deeper understanding of how hardware interrupts map to specific pins across different AVR boards, refer to the official Arduino attachInterrupt reference.
/*
* YFS201 Flow Rate Sensor - Non-Blocking Interrupt Code
* Target Board: Arduino Uno R3 (ATmega328P)
* Signal Pin: Digital 2 (INT0)
*/
const byte SENSOR_PIN = 2;
const unsigned long SAMPLE_INTERVAL = 1000; // 1 second sample window
// Volatile variables modified inside the ISR
volatile unsigned long pulseCount = 0;
unsigned long previousMillis = 0;
float flowRateLPM = 0.0;
float flowRateGPM = 0.0;
// Sensor specification: 4.5 pulses per second per L/min
const float PULSE_FACTOR = 4.5;
const float MAX_THEORETICAL_LPM = 120.0; // YFS201 max rated flow
void setup() {
Serial.begin(115200);
Serial.println("YFS201 Flow Sensor Initialized...");
// Pin 2 is configured as input. The external 10k pull-up handles the bias.
pinMode(SENSOR_PIN, INPUT);
// Attach interrupt on FALLING edge.
// Do NOT use CHANGE, or you will double-count the pulses.
attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), pulseISR, FALLING);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= SAMPLE_INTERVAL) {
previousMillis = currentMillis;
// Critical: Disable interrupts while copying the volatile variable
// to prevent a race condition if a pulse arrives mid-copy.
noInterrupts();
unsigned long safePulseCount = pulseCount;
pulseCount = 0; // Reset for the next window
interrupts();
// Calculate Frequency (Hz) = Pulses per second
float frequency = safePulseCount;
// Error Handling: Check for disconnected sensor or impossible overflow
if (frequency == 0 && flowRateLPM > 0) {
// Flow stopped
flowRateLPM = 0.0;
flowRateGPM = 0.0;
}
else if ((frequency / PULSE_FACTOR) > MAX_THEORETICAL_LPM) {
Serial.println("ERR: FLOW_OVERFLOW - Check for EMI or ghost pulses!");
}
else {
// Q (L/min) = Frequency (Hz) / 4.5
flowRateLPM = frequency / PULSE_FACTOR;
flowRateGPM = flowRateLPM * 0.264172; // Liters to US Gallons
}
// Output Data
Serial.print("Pulses: ");
Serial.print(safePulseCount);
Serial.print(" | Flow: ");
Serial.print(flowRateLPM, 2);
Serial.print(" L/min | ");
Serial.print(flowRateGPM, 2);
Serial.println(" GPM");
}
}
// Interrupt Service Routine (ISR)
void pulseISR() {
pulseCount++;
}
Debugging: First Three Things to Check When It Fails
When your serial monitor isn't behaving, don't start rewriting code. Embedded sensor failures almost always trace back to physics or pin mapping. Check these three items first:
- Symptom: "Ghost pulses" (Reading 5-15 L/min when water is completely off).
Cause: EMI from nearby wires or a floating signal pin.
Fix: Verify your 10kΩ pull-up resistor and 0.1µF capacitor are physically installed on the breadboard. The open-collector output of the YFS201 acts like an antenna without a pull-up, picking up 50/60Hz mains noise and triggering the interrupt. - Symptom: Flow rate reads exactly double the actual physical flow.
Cause: Incorrect interrupt trigger mode.
Fix: Check yourattachInterrupt()function. If you usedCHANGE, the Arduino fires the ISR on both the rising and falling edges of the square wave, doubling your count. Change it toFALLINGorRISINGto count only one edge per pulse cycle. - Symptom: Serial monitor reads 0.00 L/min while water is flowing.
Cause: Wrong interrupt vector / pin mapping mismatch.
Fix: The code above targets the Uno R3, where Pin 2 is INT0. If you uploaded this exact code to an Arduino Mega 2560, Pin 2 is not an interrupt pin (Pins 2, 3, 18, 19, 20, 21 are interrupts on the Mega). Move the yellow wire to Pin 18 on the Mega, or usedigitalPinToInterrupt()which the code already includes to handle the translation safely.
Extending and Simplifying the Build
Depending on your project phase, you might need to strip this down for a quick prototype or build it out for a permanent installation.
How to Simplify (For Quick Bench Testing)
If you don't want to deal with hardware interrupts and volatile variables, you can use the blocking pulseIn() function. While pulseIn() is generally discouraged for high-frequency signals because it halts the CPU while waiting for a pulse, it is perfectly fine for testing a YFS201 at low flow rates (under 10 L/min) where the pulse width is long enough to catch reliably. Just measure the HIGH time and LOW time, add them to get the period, and invert to get frequency.
How to Extend (For Production / IoT)
- Add Totalization: The code above calculates instantaneous flow rate. To track total volume consumed, multiply the
flowRateLPMby the time delta (1/60th of a minute) and add it to a runningtotalLitersfloat variable. - Add an Auto-Shutoff Valve: Wire a 12V DC solenoid valve via a logic-level MOSFET (like an IRLZ44N). If your total volume exceeds a set threshold, or if flow is detected while the system should be idle (leak detection), set the MOSFET gate LOW to cut the water.
- Upgrade to ESP32 for MQTT: The interrupt logic ports directly to the ESP32. Swap the Uno for an ESP32-WROOM-32, connect to WiFi, and publish the
flowRateLPMpayload to an MQTT broker like Mosquitto for integration with Home Assistant.
Frequently Asked Questions
Can I use a flow rate sensor with Arduino without a pull-up resistor?
Technically, yes, if you enable the ATmega328P's internal pull-up resistor in code using pinMode(SENSOR_PIN, INPUT_PULLUP). However, the internal pull-up is roughly 20kΩ to 50kΩ, which is often too weak to overcome heavy EMI in environments with pump motors. For reliable jobsite or permanent installations, an external 10kΩ resistor combined with a 0.1µF capacitor is the professional standard to ensure clean logic transitions.
Why is my YFS201 flow rate sensor reading double the actual flow?
This is almost always caused by setting the interrupt trigger mode to CHANGE instead of FALLING or RISING. A Hall-effect sensor outputs a square wave. CHANGE triggers the interrupt twice per cycle (once when the voltage goes high, once when it drops low). Switching to FALLING ensures you only count one complete pulse per revolution of the internal turbine.
What is the minimum flow rate the YFS201 can accurately measure on Arduino?
The YFS201 brass sensor has a physical startup threshold of about 1.5 to 2.0 Liters per minute. Below this threshold, the water pressure is insufficient to overcome the magnetic cogging and friction of the internal turbine rotor, meaning it will output 0 Hz even if a trickle of water is passing through. If your application requires measuring flows below 1 L/min, you need to switch to a smaller bore sensor like the 1/4" YF-S401, which has a lower starting threshold, or use a dedicated thermal mass flow meter.
How do I convert the Arduino flow sensor pulses to gallons per minute (GPM)?
First, convert your pulse frequency to Liters per minute (L/min) by dividing the Hz by 4.5. Then, multiply the L/min value by 0.264172 to get US Gallons per minute. The C++ code provided in this guide includes this exact conversion factor in the calculation block, outputting both metrics to the serial monitor simultaneously.






