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

Arduino-Based Force Sensor Calibration System with I2C LCD Display

View and Edit Project In Cirkit DesignerImage of Arduino-Based Force Sensor Calibration System with I2C LCD Display

Circuit Documentation

Summary

This circuit is designed to measure force using a Force Sensor (FSR) and display the readings on a 16x2 I2C LCD. The Arduino UNO microcontroller processes the sensor data and manages the communication with the LCD. A resistor is used in conjunction with the force sensor to create a voltage divider, allowing the Arduino to read the sensor's output.


Component List

Force Sensor


Arduino UNO


16x2 I2C LCD


Resistor


Wiring Details

Force Sensor


Arduino UNO


16x2 I2C LCD


Resistor


Documented Code

// FSR guided calibration with EXACTLY 4 points (conductance fit)
// Wiring: 5V -> FSR -> A0 -> 10k -> GND
// LCD: I2C 16x2 at 0x27 (change to 0x3F if needed)
// 
// Commands:
//   tare          (with no load)
//   caln <N>      (apply known force in Newtons; repeat exactly 4 times at different forces)
//   status        (show current readings and state)
//   help          (show next step)
//   tare (during streaming) restarts calibration
//
// After 4 points are collected, the fit runs automatically and streaming starts.
// Model: F ≈ m * (1/Rfsr) + c using linear regression on the 4 points.

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

LiquidCrystal_I2C lcd(0x27, 16, 2);

const uint8_t  FSR_PIN  = A0;
const float    R_FIXED  = 10000.0f;  // 10k to GND
const uint16_t ADC_MAX  = 1023;

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

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

// Exactly 4 calibration points: x = 1/R (Siemens), y = N
struct CalPoint { float x_invR; float F_N; };
const int REQ_PTS = 4;
CalPoint pts[REQ_PTS];
int nPts = 0;

long  tare_adc = 0;
float x0 = 0.0f; // 1/R at tare (for sanity checks if needed)
float m = 0.0f, c = 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(FSR_PIN);
    delay(2);
  }
  return (int)(sum / samples);
}

float fsrResistanceFromADC(int adc) {
  if (adc <= 0)       return INFINITY; // very large R at no load is expected in this divider
  if (adc >= ADC_MAX) return 0.0f;     // unexpected (A0 near 5V)
  return R_FIXED * (float(ADC_MAX) / adc - 1.0f);
}

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); }

void showIntro() {
  Serial.println(F("FSR 4-point calibration (conductance fit)"));
  Serial.println(F("Commands: tare, caln <N>, status, help"));
  Serial.println();
  sayNow(F("Ensure NO load on the FSR."));
  sayNext(F("Type: tare"));
  lcd.clear();
  lcd.setCursor(0,0); lcd.print("FSR Ready");
  lcd.setCursor(0,1); lcd.print("Type: tare");
}

void setup() {
  Serial.begin(115200); // Make sure your Serial Monitor is also set to 115200 and UTF-8/ASCII
  while (!Serial) {}

  Wire.begin();
  lcd.init();
  lcd.backlight();

  analogReference(DEFAULT);
  pinMode(FSR_PIN, INPUT);

  showIntro();
}

void printStatus() {
  int adc = readADC(6);
  float R = fsrResistanceFromADC(adc);
  Serial.print(F("stage="));
  if (stage == WAIT_TARE) Serial.print(F("WAIT_TARE"));
  else if (stage == WAIT_POINTS) Serial.print(F("WAIT_POINTS"));
  else Serial.print(F("STREAMING"));
  Serial.print(F("  pts=")); Serial.print(nPts); Serial.print(F("/4"));
  Serial.print(F("  adc=")); Serial.print(adc);
  Serial.print(F("  R(Ohm)=")); if (isfinite(R) && R > 0) Serial.print(R,0); else Serial.print(F("~INF"));
  if (stage == STREAMING) { Serial.print(F("  m=")); Serial.print(m,6); Serial.print(F(" c=")); Serial.print(c,6); }
  Serial.println();
}

void guideAfterTare() {
  Serial.println(F("Tare complete. adc near 0 at no load is normal with this wiring."));
  sayNow(F("Apply a known force and keep it steady."));
  sayNext(F("Type: caln <N> (e.g., caln 4.90). Add 4 points total."));
  lcd.clear();
  lcd.setCursor(0,0); lcd.print("Tare OK");
  lcd.setCursor(0,1); lcd.print("caln<N> x4");
}

void guideAfterPoint() {
  Serial.print(F("Point added. Now pts=")); Serial.print(nPts); Serial.println(F("/4"));
  if (nPts < REQ_PTS) {
    sayNow(F("Change to a different force level and keep steady."));
    sayNext(F("Type: caln <N> again"));
  }
}

bool fitLinear(const CalPoint* p, int n, float &mOut, float &cOut) {
  if (n < 2) return false;
  double Sx=0, Sy=0, Sxx=0, Sxy=0;
  for (int i=0;i<n;i++){ double x=p[i].x_invR, y=p[i].F_N; Sx+=x; Sy+=y; Sxx+=x*x; Sxy+=x*y; }
  double den = n*Sxx - Sx*Sx;
  if (fabs(den) <