Cirkit Designer LogoCirkit DesignerYour all-in-one circuit design IDE Launch Cirkit Designer App

Arduino-Based Infrared Proximity Sensor with I2C LCD Display

View and Edit Project In Cirkit DesignerImage of Arduino-Based Infrared Proximity Sensor with I2C LCD Display

Circuit Documentation

Summary

This circuit is designed to utilize an Arduino UNO microcontroller to interface with an Infrared Proximity Sensor and a 16x2 I2C LCD display. The system is intended for distance measurement and calibration using the infrared sensor, with results displayed on the LCD. The Arduino processes the sensor data and provides a user interface through serial commands for calibration and status updates.


Component List

Arduino UNO

16x2 I2C LCD

Infrared Proximity Sensor

Comment V2


Wiring Details

Arduino UNO


16x2 I2C LCD


Infrared Proximity Sensor


Documented Code

Arduino Code

// IR Proximity Sensor 4-point calibration (model: d ≈ a/(V - V0) + b)
// LCD: I2C 16x2 at 0x27 (change to 0x3F if needed)
// Serial commands:
//   cald <cm>   (collect exactly 4 points at different distances)
//   status
//   help
//   reset

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <math.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);   // change to 0x3F if your LCD differs

const uint8_t IR_PIN = A0;
const uint16_t ADC_MAX = 1023;
const float V_REF = 5.0f;              // UNO default analog reference

const uint8_t AVG_CAL    = 12;
const uint8_t AVG_STREAM = 8;
const unsigned long STREAM_MS = 200;

enum Stage { WAIT_POINTS, STREAMING };
Stage stage = WAIT_POINTS;

// 4 calibration points: store measured V and entered distance_cm
struct CalPoint { float V; float dist_cm; };
const int REQ_PTS = 4;
CalPoint pts[REQ_PTS];
int nPts = 0;

// Model parameters for d ~= a/(V - V0) + b
float a_par = 0.0f, b_par = 0.0f, V0_par = 0.0f;

unsigned long lastStream = 0;
String inLine;

int readADC(uint8_t samples) {
  long sum = 0;
  for (uint8_t i = 0; i < samples; i++) {
    sum += analogRead(IR_PIN);
    delay(2);
  }
  return (int)(sum / samples);
}

float adcToVoltage(int adc) {
  if (adc <= 0) return 0.0f;
  if (adc >= ADC_MAX) return V_REF;
  return (float)adc * (V_REF / ADC_MAX);
}

void sayNext(const __FlashStringHelper* msg) { Serial.print(F("NEXT: ")); Serial.println(msg); }
void sayNow (const __FlashStringHelper* msg) { Serial.print(F("DO THIS: ")); Serial.println(msg); }

// Linear regression for y = A*x + B
static bool fitLine(const float* x, const float* y, int n, float& A, float& B) {
  if (n < 2) return false;
  double Sx=0, Sy=0, Sxx=0, Sxy=0;
  for (int i=0;i<n;i++){ double xi=x[i], yi=y[i]; Sx+=xi; Sy+=yi; Sxx+=xi*xi; Sxy+=xi*yi; }
  double den = n*Sxx - Sx*Sx;
  if (fabs(den) < 1e-12) return false;
  A = (float)((n*Sxy - Sx*Sy)/den);
  B = (float)((Sy - A*Sx)/n);
  return true;
}

// Evaluate RMS error for a candidate V0 by linearizing x_i = 1/(V_i - V0) and fitting d = a*x + b
static bool tryV0(const CalPoint* p, int n, float V0, float& aOut, float& bOut, float& rms) {
  float x[REQ_PTS], y[REQ_PTS];
  for (int i=0;i<n;i++){
    float Vi = p[i].V;
    if (!(Vi > V0 + 1e-4f)) return false; // avoid division by near-zero or negative
    x[i] = 1.0f / (Vi - V0);
    y[i] = p[i].dist_cm;
  }
  float a,b;
  if (!fitLine(x, y, n, a, b)) return false;
  double err2=0.0;
  for (int i=0;i<n;i++){
    double di = (double)a * x[i] + b;
    double e = di - y[i];
    err2 += e*e;
  }
  rms = (float)sqrt(err2 / n);
  aOut = a; bOut = b;
  return true;
}

// Search V0 in a reasonable range to minimize RMS (coarse + refine)
bool fitNonlinearModel(const CalPoint* p, int n, float& a, float& b, float& V0) {
  if (n < 3) return false;

  // Define search bounds for V0. Use data-driven limits slightly below min(V) and near 0 V.
  float vmin = 10.0f, vmax = -1.0f;
  for (int i=0;i<n;i++){ vmin = min(vmin, p[i].V); vmax = max(vmax, p[i].V); }
  if (!(vmin < vmax)) return false;

  float lower = max(0.0f, vmin - 0.8f);   // allow an offset less than the smallest measured V
  float upper = min(vmin - 0.05f, V_REF*0.95f); // V0 must be at least 50 mV below min(V)
  if (!(upper > lower)) { // fallback generic range
    lower = 0.0f;
    upper = max(0.2f, vmin - 0.05f);
  }

  float bestA=0, bestB=0, bestV0=lower, bestRMS=1e9f;

  // Coarse grid
  const int N1 = 60;
  for (int i=0;i<=N1;i++){
    float v0 = lower + (upper - lower) * (float)i / (float)N1;
    float a1,b1,r1;
    if (tryV0(p, n, v0, a1, b1, r1)) {
      if (r1 < bestRMS) { bestRMS = r1; bestA=a1; bestB=b1; bestV0=v0; }
    }
  }

  // Refine around bestV0
  float span = (upper - lower) * 0.1f;
  float lo = max(0.0f, bestV0 - span);
  float hi = min(bestV0 + span, V_REF*0.98f);
  const int N2 = 80;
  for (int i=0;i<=N2;i++){
    float v0 = lo + (hi - lo) * (float)i / (float)N2;
    float a1,b1,r1;
    if