Full arduino codebase ultrasonic counts (without flume-base) with signal-processing for better fish/bubble separation for counting catfish in biofloc environment where pond is 1 meter deep and aeration is 10 liters/minute and floc is 50ml/liter.

/*
  Open‐Pond Ultrasonic Catfish Counter
  ———————————–
  Counts catfish echoes in a 1 m–deep biofloc pond (~50 mL/L solids) under
  10 L/min aeration without using a flume. Implements:

    • Automatic baseline calibration (pond bottom distance) 
    • Burst of PING_COUNT pings → median‐filter gating 
    • Fish/gas‐bubble separation via distance gating & median filter 
    • DROP_CM below bottom → fish event 
    • DEBOUNCE_MS to avoid double counts 
    • Periodic re‐baseline every RECALIBRATE_INTERVAL_S 
    • Logs “Fish #n @ t.s” over Serial (swap-in SD/MQTT easily)

  Hardware (HC-SR04):
    TRIG → D7
    ECHO → D6
    VCC  → 5 V
    GND  → GND
*/

#include <Arduino.h>

// ── USER CONFIG ──────────────────────────────────────────────────────────────
// pins
const uint8_t PIN_TRIG = 7;
const uint8_t PIN_ECHO = 6;

// signal‐processing parameters
const uint8_t  MAX_BASELINE_SAMPLES   = 30;   // samples to calibrate bottom
const uint8_t  PING_COUNT             = 7;    // pings per detection burst
const float    DROP_CM                = 8.0;  // drop from bottom → fish
const float    MIN_DIST_CM            = 20.0; // ignore echoes closer than this
const float    MAX_DIST_CM            = 90.0; // ignore echoes deeper than this
const uint16_t DEBOUNCE_MS            = 300;  // lockout after a detection
const uint32_t RECALIBRATE_INTERVAL_S = 600;  // re‐baseline every 10 min

// runtime state
float    bottomDist   = 0.0;   // calibrated pond‐bottom distance (cm)
float    detectThresh = 0.0;   // bottomDist – DROP_CM
uint32_t fishCount    = 0;     // total fish counted
uint32_t lastFishTS   = 0;     // timestamp of last detection
uint32_t lastRecalTS  = 0;     // timestamp of last re‐baseline

void setup() {
  Serial.begin(115200);
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);

  Serial.println(F(“\n⟳ Calibrating bottom baseline…”));
  bottomDist   = calibrateBaseline();
  detectThresh = bottomDist – DROP_CM;
  lastRecalTS  = millis();

  Serial.printf(“BottomDist=%.1f cm → fish if < %.1f cm\n▶ Start counting\n\n”,
                bottomDist, detectThresh);
}

void loop() {
  uint32_t now = millis();

  // 1) Periodic re‐baseline
  if (now – lastRecalTS >= RECALIBRATE_INTERVAL_S * 1000UL) {
    Serial.println(F(“⟳ Recalibrating baseline…”));
    bottomDist   = calibrateBaseline();
    detectThresh = bottomDist – DROP_CM;
    lastRecalTS  = now;
    Serial.printf(“New BottomDist=%.1f cm thresh<%.1f cm\n\n”,
                  bottomDist, detectThresh);
  }

  // 2) Burst of ultrasonic pings
  float readings[PING_COUNT];
  for (uint8_t i = 0; i < PING_COUNT; i++) {
    readings[i] = measureDistance();
    delay(20);  // ~50 Hz burst to outrun bubble motion
  }

  // 3) Median‐filter to reject transient bubble/floc echoes
  float med = medianFilter(readings, PING_COUNT);

  // 4) Distance gating + fish detection + debounce
  if (med > MIN_DIST_CM && med < detectThresh
      && (now – lastFishTS > DEBOUNCE_MS)) {
    fishCount++;
    lastFishTS = now;
    Serial.printf(“🐟 Fish #%lu @ %.2f s\n”,
                  fishCount, now / 1000.0f);
  }

  delay(100);
}

// ── UTILITIES ────────────────────────────────────────────────────────────────

// send one HC-SR04 ping; return cm or -1 on timeout
float measureDistance() {
  digitalWrite(PIN_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(PIN_TRIG, LOW);

  long us = pulseIn(PIN_ECHO, HIGH, 30000);  // 30 ms timeout
  if (us == 0) return -1.0;
  return (us / 2.0) / 29.1;                 // µs→cm
}

// average MAX_BASELINE_SAMPLES valid pings for bottomDist
float calibrateBaseline() {
  float sum = 0;
  uint8_t cnt = 0;
  while (cnt < MAX_BASELINE_SAMPLES) {
    float d = measureDistance();
    if (d > 0) {
      sum += d;
      cnt++;
    }
    delay(100);
  }
  return sum / cnt;
}

// simple insertion sort → return median element
float medianFilter(float *arr, uint8_t n) {
  for (uint8_t i = 1; i < n; i++) {
    float key = arr[i];
    int8_t j = i – 1;
    while (j >= 0 && arr[j] > key) {
      arr[j + 1] = arr[j];
      j–;
    }
    arr[j + 1] = key;
  }
  return arr[n / 2];
}

Signal-Processing Tip

  • Fire a burst of 7 pings at ~50 Hz and take the median: this rejects one-off high/low spikes from fast-rising bubbles or dense floc clumps.
  • Only count echoes between MINDISTCM and detectThresh to ignore surface/bottom echoes.
  • Periodically re-baseline (every 10 min) to track slow drift in temperature or floc buildup, keeping your DROP_CM threshold accurate under heavy aeration and 50 mL/L solids.

/*
  Open-Pond Ultrasonic Catfish Counter
  ———————————–
  Counts catfish echoes in a 1 m-deep biofloc pond (~50 mL/L solids)
  under 10 L/min aeration—no flume required.

  • Auto-calibrates bottom distance (MAX_BASELINE_SAMPLES)
  • Fires a burst of PING_COUNT pings → median-filter gating
  • Only counts echoes between MIN_DIST_CM and (bottomDist-DROP_CM)
  • DEBOUNCE_MS locks out after each detection
  • Periodic re-baseline every RECALIBRATE_INTERVAL_S
  • Logs “Fish #n @ t.s” on Serial (swap for SD/MQTT as needed)

  HC-SR04 wiring:
    TRIG → D7
    ECHO → D6
    VCC  → 5 V
    GND  → GND
*/

#include <Arduino.h>

// ── USER CONFIG ──────────────────────────────────────────────────────────────
const uint8_t  PIN_TRIG               = 7;      // HC-SR04 TRIG pin
const uint8_t  PIN_ECHO               = 6;      // HC-SR04 ECHO pin

const uint8_t  MAX_BASELINE_SAMPLES   = 30;     // samples for bottom calibration
const uint8_t  PING_COUNT             = 7;      // pings per detection burst
const float    DROP_CM                = 8.0;    // drop below bottom → fish event
const float    MIN_DIST_CM            = 20.0;   // ignore echoes closer than this (cm)
const float    MAX_DIST_CM            = 95.0;   // ignore echoes deeper than this (cm)
const uint16_t DEBOUNCE_MS            = 300;    // ms lockout per fish
const uint32_t RECALIBRATE_INTERVAL_S = 600;    // seconds between re‐baselines

// ── RUNTIME STATE ────────────────────────────────────────────────────────────
float    bottomDist   = 0.0;   // calibrated pond bottom distance (cm)
float    detectThresh = 0.0;   // bottomDist − DROP_CM
uint32_t fishCount    = 0;     // total fish counted
uint32_t lastFishTS   = 0;     // millis() of last count
uint32_t lastRecalTS  = 0;     // millis() of last baseline

// ── SETUP ────────────────────────────────────────────────────────────────────
void setup() {
  Serial.begin(115200);
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);

  Serial.println(F(“\n⟳ Calibrating bottom baseline…”));
  bottomDist   = calibrateBaseline();
  detectThresh = bottomDist – DROP_CM;
  lastRecalTS  = millis();

  Serial.printf(“BottomDist=%.1f cm → count if < %.1f cm\n▶ Counting started\n\n”,
                bottomDist, detectThresh);
}

// ── MAIN LOOP ────────────────────────────────────────────────────────────────
void loop() {
  uint32_t now = millis();

  // 1) Periodic bottom re-calibration
  if (now – lastRecalTS >= RECALIBRATE_INTERVAL_S * 1000UL) {
    Serial.println(F(“⟳ Re-calibrating bottom baseline…”));
    bottomDist   = calibrateBaseline();
    detectThresh = bottomDist – DROP_CM;
    lastRecalTS  = now;
    Serial.printf(“New BottomDist=%.1f cm → thresh<%.1f cm\n\n”,
                  bottomDist, detectThresh);
  }

  // 2) Burst of ultrasonic pings
  float readings[PING_COUNT];
  for (uint8_t i = 0; i < PING_COUNT; i++) {
    readings[i] = measureDistance();
    delay(20);  // ≈50 Hz to outrun bubble motion
  }

  // 3) Median-filter gating
  float med = medianFilter(readings, PING_COUNT);

  // 4) Fish detection + debounce + distance gating
  if (med > MIN_DIST_CM && med < detectThresh &&
      (now – lastFishTS > DEBOUNCE_MS)) {
    fishCount++;
    lastFishTS = now;
    Serial.printf(“ Fish #%lu @ %.2f s\n”,
                  fishCount, now / 1000.0f);
  }

  delay(100);
}

// ── UTILITY FUNCTIONS ─────────────────────────────────────────────────────────

// Send one HC-SR04 ping → return cm or –1 on timeout
float measureDistance() {
  digitalWrite(PIN_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(PIN_TRIG, LOW);

  long duration = pulseIn(PIN_ECHO, HIGH, 30000);  // 30 ms timeout
  if (duration == 0) return -1.0;
  return (duration / 2.0) / 29.1;                  // µs → cm
}

// Calibrate bottom distance: average MAX_BASELINE_SAMPLES valid readings
float calibrateBaseline() {
  float sum = 0;
  uint8_t cnt = 0;
  while (cnt < MAX_BASELINE_SAMPLES) {
    float d = measureDistance();
    if (d > 0 && d < MAX_DIST_CM) {
      sum += d;
      cnt++;
    }
    delay(100);
  }
  return sum / cnt;
}

// Simple insertion sort + return middle element as median
float medianFilter(float *arr, uint8_t n) {
  for (uint8_t i = 1; i < n; i++) {
    float key = arr[i];
    int8_t j = i – 1;
    while (j >= 0 && arr[j] > key) {
      arr[j + 1] = arr[j];
      j–;
    }
    arr[j + 1] = key;
  }
  return arr[n / 2];
}

Signal-Processing Tip

  • Fire a burst of 7 pings at ~50 Hz and take the median: this rejects one-off spikes from rising bubbles or dense floc.
  • Gate out echoes closer than MINDISTCM (surface bubbles) and deeper than detectThresh (pond bottom), so only fish returns remain.
  • Re-baseline every 10 min to track slow drifts in temperature or floc buildup, keeping your DROP_CM threshold accurate under 10 L/min aeration.

Here’s a turnkey Arduino sketch for counting catfish passing single-file through a 6–8 cm flume, optimized for a 1 m-deep biofloc pond (≈50 mL/L solids) with 10 L/min aeration. It:

  • Calibrates a dynamic baseline to the opposite wall every 10 min
  • Fires bursts of ultrasonic pings (you’ll lose many bubble echoes)
  • Uses a median filter across each burst to reject transient floc/bubble spikes
  • Flags a fish when the median drops by DROP_CM
  • Debounces to ensure one count per fish
  • Logs “Fish #n @ t s” over Serial (swap in SD or MQTT as needed)

/*
  Biofloc Ultrasonic Catfish Counter
  Pond depth: 1 m  |  Biofloc: ~50 mL/L  |  Aeration: 10 L/min

  Hardware:
    • HC-SR04 ultrasonic sensor (2–400 cm range)
    • Mounted across a 6–8 cm PVC/acrylic flume
    • TRIG → D7, ECHO → D6, VCC → 5 V, GND → GND
    • (Optional bubble-skirt 3D-printed around transducer)

  Features:
    • Auto baseline calibration (MAX_READS samples)
    • Burst of PINGS readings → median filter for bubble/floc rejection
    • DROP_CM below baseline → fish event
    • DEBOUNCE_MS to avoid double-counts
    • Re-calibrate baseline every RECALIBRATE_S seconds
*/

#include <Arduino.h>

// USER CONFIGURATION
const uint8_t  PIN_TRIG       = 7;
const uint8_t  PIN_ECHO       = 6;

const uint8_t  MAX_READS      = 30;      // samples for baseline
const uint8_t  PINGS          = 7;       // pings per detection burst
const float    DROP_CM        = 6.0;     // cm drop = fish event
const uint16_t DEBOUNCE_MS    = 250;     // ms ignore window
const uint32_t RECALIBRATE_S  = 600;     // seconds between auto-recalibrations

// RUNTIME STATE
float    baselineDist  = 0.0;
float    detectThresh  = 0.0;
uint32_t fishCount     = 0;
uint32_t lastFishTS    = 0;
uint32_t lastRecalTS   = 0;

void setup() {
  Serial.begin(115200);
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);

  Serial.println(F(“\n⟳ Calibrating baseline…”));
  baselineDist = calibrateBaseline();
  detectThresh = baselineDist – DROP_CM;
  lastRecalTS  = millis();

  Serial.printf(“Baseline = %.1f cm  → detect if < %.1f cm\n”,
                baselineDist, detectThresh);
  Serial.println(F(“▶ Start counting\n”));
}

void loop() {
  uint32_t now = millis();

  // Auto-recalibrate baseline periodically
  if (now – lastRecalTS >= RECALIBRATE_S * 1000UL) {
    Serial.println(F(“⟳ Re-calibrating baseline…”));
    baselineDist = calibrateBaseline();
    detectThresh = baselineDist – DROP_CM;
    lastRecalTS  = now;
    Serial.printf(“New baseline = %.1f cm  Thresh = < %.1f cm\n\n”,
                  baselineDist, detectThresh);
  }

  // 1) Burst-ping and store readings
  float buf[PINGS];
  for (uint8_t i = 0; i < PINGS; i++) {
    buf[i] = measureDist();
    delay(20);  // ~50 Hz burst to outrun bubble motion
  }

  // 2) Median filter to reject transient bubble/floc spikes
  float med = median(buf, PINGS);

  // 3) Fish detection & debounce
  if (med > 0 && med < detectThresh
      && (now – lastFishTS > DEBOUNCE_MS)) {
    fishCount++;
    lastFishTS = now;
    Serial.printf(“Fish #%lu @ %.2f s\n”,
                  fishCount, now / 1000.0f);
  }

  delay(100);
}

// SENDS ONE PING, RETURNS cm OR –1 ON TIMEOUT
float measureDist() {
  digitalWrite(PIN_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(PIN_TRIG, LOW);

  long us = pulseIn(PIN_ECHO, HIGH, 30000); // 30 ms timeout
  if (us == 0) return -1.0;
  return (us / 2.0) / 29.1;                 // µs→cm
}

// AVERAGES MAX_READS VALID PINGS FOR BASELINE
float calibrateBaseline() {
  float sum = 0;
  uint8_t cnt = 0;
  while (cnt < MAX_READS) {
    float d = measureDist();
    if (d > 0) {
      sum += d;
      cnt++;
    }
    delay(100);
  }
  return sum / cnt;
}

// SIMPLE INSERTION SORT → RETURN MIDDLE ELEMENT
float median(float *arr, uint8_t n) {
  for (uint8_t i = 1; i < n; i++) {
    float key = arr[i];
    int8_t j = i – 1;
    while (j >= 0 && arr[j] > key) {
      arr[j+1] = arr[j];
      j–;
    }
    arr[j+1] = key;
  }
  return arr[n/2];
}

/*
  Biofloc Ultrasonic Catfish Counter
  Pond depth: 1 m | Aeration: 10 L/min | Floc: ~50 mL/L

  – HC-SR04 mounted across a 6–8 cm PVC flume
  – TRIG→D7, ECHO→D6, VCC→5 V, GND→GND
  – Optional bubble-skirt around transducer

  Features:
   1. Auto baseline (MAX_READS)
   2. Burst of PINGS → median filter
   3. DROP_CM below baseline → fish event
   4. DEBOUNCE_MS per-fish
   5. Re-baseline every RECALIBRATE_S seconds
*/

#include <Arduino.h>

// USER SETTINGS
const uint8_t PIN_TRIG      = 7;
const uint8_t PIN_ECHO      = 6;
const uint8_t MAX_READS     = 30;      // baseline samples
const uint8_t PINGS         = 7;       // pings per burst
const float   DROP_CM       = 6.0;     // cm drop = fish event
const uint16_t DEBOUNCE_MS  = 250;     // ms ignore window
const uint32_t RECALIB_S    = 600;     // baseline recal every 10 min

// STATE
float   baselineDist = 0, detectThresh = 0;
uint32_t fishCount = 0, lastFishTS = 0, lastRecalTS = 0;

void setup() {
  Serial.begin(115200);
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);

  Serial.println(“\n⟳ Calibrating baseline…”);
  baselineDist  = calibrateBaseline();
  detectThresh  = baselineDist – DROP_CM;
  lastRecalTS   = millis();

  Serial.printf(“Baseline=%.1f cm → thresh<%.1f cm\n▶ Starting counts\n\n”,
                baselineDist, detectThresh);
}

void loop() {
  uint32_t now = millis();

  // periodic baseline recalibration
  if (now – lastRecalTS >= RECALIB_S*1000UL) {
    Serial.println(“⟳ Re-calibrating baseline…”);
    baselineDist = calibrateBaseline();
    detectThresh = baselineDist – DROP_CM;
    lastRecalTS  = now;
    Serial.printf(“New baseline=%.1f cm thresh<%.1f cm\n\n”,
                  baselineDist, detectThresh);
  }

  // 1) burst-ping
  float buf[PINGS];
  for (uint8_t i = 0; i < PINGS; i++) {
    buf[i] = measureDist();
    delay(20);  // 50 Hz
  }

  // 2) median filter
  float m = median(buf, PINGS);

  // 3) detect + debounce
  if (m > 0 && m < detectThresh && now – lastFishTS > DEBOUNCE_MS) {
    fishCount++;
    lastFishTS = now;
    Serial.printf(“🐟 #%lu @ %.2f s\n”, fishCount, now/1000.0f);
  }

  delay(100);
}

// — measureDist() — one HC-SR04 ping → cm or –1
float measureDist() {
  digitalWrite(PIN_TRIG, LOW);  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(PIN_TRIG, LOW);
  long us = pulseIn(PIN_ECHO, HIGH, 30000);
  if (!us) return -1;
  return (us/2.0) / 29.1;
}

// — calibrateBaseline() — avg of MAX_READS valid pings
float calibrateBaseline() {
  float sum = 0; uint8_t cnt = 0;
  while (cnt < MAX_READS) {
    float d = measureDist();
    if (d > 0) { sum += d; cnt++; }
    delay(100);
  }
  return sum / cnt;
}

// — median() — insertion-sort & return arr[n/2]
float median(float *arr, uint8_t n) {
  for (uint8_t i = 1; i < n; i++) {
    float k = arr[i]; int8_t j = i-1;
    while (j>=0 && arr[j] > k) { arr[j+1] = arr[j]; j–; }
    arr[j+1] = k;
  }
  return arr[n/2];
}

/*
  Biofloc Ultrasonic Catfish Counter
  ———————————-
  Counts catfish passing single‐file through a 6–8cm flume in a 1m-deep biofloc pond
  (≈50 mL/L solids) with 10 L/min aeration. 
  Implements:
    • Auto baseline calibration (MAX_READS samples) 
    • Burst of PINGS ultrasonic pings → median filter for bubble/floc rejection 
    • DROP_CM below baseline → fish event 
    • DEBOUNCE_MS to avoid double-counts 
    • Periodic baseline re-calibration every RECALIBRATE_S seconds 
    • Serial log: “Fish #n @ t.s” (swap for SD or MQTT as needed) 
*/

#include <Arduino.h>

// ── USER CONFIG ──────────────────────────────────────────────────────────────
const uint8_t  PIN_TRIG      = 7;        // HC-SR04 TRIG pin
const uint8_t  PIN_ECHO      = 6;        // HC-SR04 ECHO pin

const uint8_t  MAX_READS     = 30;       // calibration pings count
const uint8_t  PINGS         = 7;        // pings per detection burst
const float    DROP_CM       = 6.0;      // cm drop from baseline → fish
const uint16_t DEBOUNCE_MS   = 250;      // ms ignore after a count
const uint32_t RECALIBRATE_S = 600;      // seconds between auto-recalibrations

// ── RUNTIME STATE ────────────────────────────────────────────────────────────
float    baselineDist  = 0.0;            // calibrated avg distance (cm)
float    detectThresh  = 0.0;            // baselineDist – DROP_CM
uint32_t fishCount     = 0;              // total fish counted
uint32_t lastFishTS    = 0;              // timestamp of last count
uint32_t lastRecalTS   = 0;              // timestamp of last recalibration

// ── SETUP ────────────────────────────────────────────────────────────────────
void setup() {
  Serial.begin(115200);
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);

  Serial.println(F(“\n⟳ Calibrating baseline distance…”));
  baselineDist = calibrateBaseline();
  detectThresh = baselineDist – DROP_CM;
  lastRecalTS  = millis();

  Serial.printf(“Baseline = %.1f cm   → detect if < %.1f cm\n”,
                baselineDist, detectThresh);
  Serial.println(F(“▶ Starting fish counting…\n”));
}

// ── MAIN LOOP ────────────────────────────────────────────────────────────────
void loop() {
  uint32_t now = millis();

  // 1) Periodic re-calibration
  if (now – lastRecalTS >= RECALIBRATE_S * 1000UL) {
    Serial.println(F(“\n⟳ Re-calibrating baseline…”));
    baselineDist = calibrateBaseline();
    detectThresh = baselineDist – DROP_CM;
    lastRecalTS  = now;
    Serial.printf(“New baseline = %.1f cm   Thresh = < %.1f cm\n\n”,
                  baselineDist, detectThresh);
  }

  // 2) Burst of ultrasonic pings
  float readings[PINGS];
  for (uint8_t i = 0; i < PINGS; i++) {
    readings[i] = measureDistance();
    delay(20);  // 50 Hz burst to outrun bubble movement
  }

  // 3) Median filter to reject transient echoes
  float med = medianFilter(readings, PINGS);

  // 4) Fish detection + debounce
  if (med > 0 && med < detectThresh &&
      (now – lastFishTS > DEBOUNCE_MS)) {
    fishCount++;
    lastFishTS = now;
    Serial.printf(“🐟 Fish #%lu @ %.2f s\n”,
                  fishCount, now / 1000.0f);
  }

  delay(100);
}

// ── FUNCTIONS ────────────────────────────────────────────────────────────────

// measureDistance(): sends an HC-SR04 ping, returns cm or –1 on timeout
float measureDistance() {
  digitalWrite(PIN_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(PIN_TRIG, LOW);

  long duration = pulseIn(PIN_ECHO, HIGH, 30000);  // 30 ms timeout
  if (duration == 0) return -1.0;
  return (duration / 2.0) / 29.1;                 // µs→cm
}

// calibrateBaseline(): averages MAX_READS valid readings for baseline
float calibrateBaseline() {
  float sum = 0;
  uint8_t count = 0;
  while (count < MAX_READS) {
    float d = measureDistance();
    if (d > 0) {
      sum += d;
      count++;
    }
    delay(100);
  }
  return sum / count;
}

// medianFilter(): simple insertion sort → returns middle element
float medianFilter(float *arr, uint8_t n) {
  for (uint8_t i = 1; i < n; i++) {
    float key = arr[i];
    int8_t j = i – 1;
    while (j >= 0 && arr[j] > key) {
      arr[j + 1] = arr[j];
      j–;
    }
    arr[j + 1] = key;
  }
  return arr[n / 2];
}

Signal-Processing Tip
By firing 7 quick pings at ~50 Hz and taking the median distance, you suppress one-off echoes from fast-rising bubbles or dense floc clumps. Meanwhile, periodic re-baseline calibration tracks slow drifts (temperature, floc buildup) so your DROP_CM threshold stays tuned over long runs under 10 L/min aeration.

/*
  Biofloc Ultrasonic Catfish Counter
  Pond depth: 1 m, biofloc ~50 mL/L, heavy aeration

  Hardware:
    • HC-SR04 or equivalent narrow-beam ultrasonic module
    • Mounted across a 6–8 cm flume channel (PVC/acrylic)
    • TRIG → D7, ECHO → D6, VCC → 5 V, GND → GND

  Features:
    1) Automatic baseline calibration (MAX_READS samples)
    2) Detection bursts of PINGS readings → median filtering
    3) Dynamic threshold: DROP_CM below baseline
    4) DEBOUNCE_MS to avoid double-counts
    5) Runtime re-calibration every RECALIBRATE_S seconds
*/

#include <Arduino.h>

//── USER CONFIG ─────────────────────────────────────────────────────────────
const uint8_t  PIN_TRIG        = 7;
const uint8_t  PIN_ECHO        = 6;

const uint8_t  MAX_READS       = 30;      // baseline samples
const uint8_t  PINGS           = 7;       // pings per detection burst
const float    DROP_CM         = 6.0;     // cm drop = fish event
const uint16_t DEBOUNCE_MS     = 250;     // ms ignore window
const uint32_t RECALIBRATE_S   = 600;     // re-calibrate baseline every 10 min

//── RUNTIME STATE ────────────────────────────────────────────────────────────
float   baselineDist   = 0.0;
float   detectThresh   = 0.0;
uint32_t fishCount     = 0;
uint32_t lastFishTS    = 0;
uint32_t lastRecalTS   = 0;

//── SETUP ────────────────────────────────────────────────────────────────────
void setup() {
  Serial.begin(115200);
  pinMode(PIN_TRIG, OUTPUT);
  pinMode(PIN_ECHO, INPUT);

  Serial.println(F(“\n— Calibrating baseline distance —”));
  baselineDist = calibrateBaseline();
  detectThresh = baselineDist – DROP_CM;
  lastRecalTS  = millis();

  Serial.printf(“Baseline: %.1f cm  ➔  Threshold: < %.1f cm\n”,
                baselineDist, detectThresh);
  Serial.println(F(“— START COUNTING —\n”));
}

//── LOOP ─────────────────────────────────────────────────────────────────────
void loop() {
  uint32_t now = millis();

  // Periodic baseline re-calibration
  if (now – lastRecalTS >= RECALIBRATE_S * 1000UL) {
    baselineDist = calibrateBaseline();
    detectThresh = baselineDist – DROP_CM;
    lastRecalTS  = now;
    Serial.printf(“Re-calibrated baseline: %.1f cm  Thresh: < %.1f cm\n”,
                  baselineDist, detectThresh);
  }

  // 1) Burst pings → store readings
  float buf[PINGS];
  for (uint8_t i = 0; i < PINGS; i++) {
    buf[i] = measureDist();
    delay(20);  // 50 Hz ping burst
  }

  // 2) Median filter to reject bubbles/floc
  float med = median(buf, PINGS);

  // 3) Fish detection + debounce
  if (med > 0 && med < detectThresh && (now – lastFishTS > DEBOUNCE_MS)) {
    fishCount++;
    lastFishTS = now;
    Serial.printf(“Fish #%lu @ %.2f s\n”,
                  fishCount, now / 1000.0f);
  }

  delay(100);
}

//── MEASURE DISTANCE ──────────────────────────────────────────────────────────
// Sends one HC-SR04 ping, returns distance in cm or -1 on timeout
float measureDist() {
  digitalWrite(PIN_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(PIN_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(PIN_TRIG, LOW);

  long us = pulseIn(PIN_ECHO, HIGH, 30000);  // 30 ms timeout
  if (us == 0) return -1.0;
  return (us / 2.0) / 29.1;
}

//── BASELINE CALIBRATION ─────────────────────────────────────────────────────
// Averages MAX_READS valid pings for a stable baseline
float calibrateBaseline() {
  float sum = 0;
  uint8_t cnt = 0;
  while (cnt < MAX_READS) {
    float d = measureDist();
    if (d > 0) {
      sum += d;
      cnt++;
    }
    delay(100);
  }
  return sum / cnt;
}

//── MEDIAN FILTER ─────────────────────────────────────────────────────────────
// Simple insertion sort + return middle value
float median(float *arr, uint8_t n) {
  for (uint8_t i = 1; i < n; i++) {
    float key = arr[i];
    int8_t j = i – 1;
    while (j >= 0 && arr[j] > key) {
      arr[j+1] = arr[j];
      j–;
    }
    arr[j+1] = key;
  }
  return arr[n/2];
}

It:
• Calibrates a dynamic baseline to the back-wall reflector 
• Fires bursts of PINGS ultrasonic readings, applies a median filter to reject bubble/floc spikes 
• Flags a “fish event” when the median drops by DROP_CM 
• Debounces so you get exactly one count per crossing 
• Logs “Fish #n @ t s” to Serial (swap for SD/MQTT as needed) 

0 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Copyright © 2026 belajar (kembali) menjadi hamba | Powered by eatmaja.co.id