3-Mode Headlight Controller
- Manual mode (driver uses a toggle to pick high/low)
- Auto mode (LDR switches beams automatically)
- GPS mode (if inside geofence, force LOW beam)
- If more than one mode is selected -> show "MULTIPLE MODES" and set LOW beam
Libraries required:
- TinyGPSPlus
- LiquidCrystal_I2C
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <TinyGPSPlus.h>
#include <SoftwareSerial.h>
// -------------------- USER CONFIG --------------------
// Set the geofence center (replace with your target coordinates)
const double GEOFENCE_LAT = 28.6139; // example: New Delhi lat (replace)
const double GEOFENCE_LON = 77.2090; // example: New Delhi lon (replace)
const double GEOFENCE_RADIUS_METERS = 2000.0; // radius in meters
// I2C address of LCD - change if your module uses different address (0x27 or 0x3F)
// 16 cols x 2 rows LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);
// GPS (SoftwareSerial)
static const uint8_t GPS_RX_PIN = 10; // connect to GPS TX
static const uint8_t GPS_TX_PIN = 11; // connect to GPS RX
SoftwareSerial gpsSerial(GPS_RX_PIN, GPS_TX_PIN);
TinyGPSPlus gps;
// Pin assignments
const int pinLDR = 2; // LDR DO -> digital read
const int pinManualBeam = 3; // Manual beam toggle (HIGH/LOW) - used only when Manual mode active
const int pinModeManual = 4; // Mode toggle: Manual mode (SPST toggle)
const int pinModeAuto = 5; // Mode toggle: Auto (LDR)
const int pinModeGPS = 6; // Mode toggle: GPS mode
const int relayHighPin = 8; // Relay IN1 - HIGH beam
const int relayLowPin = 9; // Relay IN2 - LOW beam
// Timing
unsigned long lastGpsCheck = 0;
const unsigned long GPS_CHECK_INTERVAL = 2000; // ms
// -------------------- SETUP --------------------
void setup() {
Serial.begin(115200);
gpsSerial.begin(9600); // many GPS modules use 9600
lcd.init();
lcd.backlight();
// Pin modes
pinMode(pinLDR, INPUT); // LDR module DO
pinMode(pinManualBeam, INPUT_PULLUP); // using internal pull-up; switch to GND when ON
// mode switches - using internal pullups (switch to GND when ON)
pinMode(pinModeManual, INPUT_PULLUP);
pinMode(pinModeAuto, INPUT_PULLUP);
pinMode(pinModeGPS, INPUT_PULLUP);
pinMode(relayHighPin, OUTPUT);
pinMode(relayLowPin, OUTPUT);
// Default: LOW beam ON
digitalWrite(relayHighPin, LOW);
digitalWrite(relayLowPin, HIGH);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Headlight System");
lcd.setCursor(0,1);
lcd.print("Initializing...");
delay(1200);
}
// -------------------- MAIN LOOP --------------------
void loop() {
// read incoming GPS serial into TinyGPS parser
while (gpsSerial.available()) {
gps.encode(gpsSerial.read());
}
// read mode toggles (active LOW because of INPUT_PULLUP)
bool modeManual = (digitalRead(pinModeManual) == LOW);
bool modeAuto = (digitalRead(pinModeAuto) == LOW);
bool modeGPS = (digitalRead(pinModeGPS) == LOW);
int modesOn = (modeManual ? 1 : 0) + (modeAuto ? 1 : 0) + (modeGPS ? 1 : 0);
// If more than one mode selected -> error state, force LOW beam
if (modesOn > 1) {
setLowBeam();
lcdMultipleModes();
return; // quickly go back to start of loop
}
// No mode selected -> default safe LOW beam
if (modesOn == 0) {
setLowBeam();
lcdShow("MODE: NONE", "Beam: LOW");
return;
}
// Exactly one mode selected
if (modeManual) {
handleManualMode();
} else if (modeAuto) {
handleAutoMode();
} else if (modeGPS) {
handleGPSMode();
}
}
// -------------------- MODE HANDLERS --------------------
void handleManualMode() {
// manualBeam toggle: using INPUT_PULLUP -> LOW = switched ON for "HIGH" maybe; choose mapping:
// Assume: manualBeam switch ON = HIGH beam, OFF = LOW beam. Adjust as you prefer.
bool manualHigh = (digitalRead(pinManualBeam) == LOW); // switch closed -> LOW -> treat as HIGH beam
if (manualHigh) {
setHighBeam();
lcdShow("MODE: MANUAL", "Beam: HIGH");
} else {
setLowBeam();
lcdShow("MODE: MANUAL", "Beam: LOW");
}
}
void handleAutoMode() {
// LDR DO: DO == LOW when bright light detected (incoming headlight)
int ldrState = digitalRead(pinLDR);
if (ldrState == LOW) {
// bright light -> switch to LOW beam
setLowBeam();
lcdShow("MODE: AUTO(LDR)", "Beam: LOW");
} else {
// dark road -> HIGH beam
setHighBeam();
lcdShow("MODE: AUTO(LDR)", "Beam: HIGH");
}
}
void handleGPSMode() {
// Check GPS fix & position (do not block; check periodically)
unsigned long now = millis();
if (now - lastGpsCheck < GPS_CHECK_INTERVAL) {
// avoid checking too fast; but still show previous status
// We'll show last known position/beam state by calling evaluateGPS once per interval in next run
// For simplicity, run evaluation now anyway if data available
}
lastGpsCheck = now;
if (!gps.location.isValid()) {
// No fix yet
setLowBeam(); // safe default
lcdShow("MODE: GPS", "No GPS Fix - LOW");
return;
}
double lat = gps.location.lat();
double lon = gps.location.lng();
double dist = distanceMeters(lat, lon, GEOFENCE_LAT, GEOFENCE_LON);
if (dist <= GEOFENCE_RADIUS_METERS) {
// inside geofence -> force LOW beam
setLowBeam();
char line2[17];
snprintf(line2, 17, "LOW in zone %dm", (int)dist);
lcdShow("MODE: GPS", line2);
} else {
// outside geofence -> allow HIGH beam
setHighBeam();
char line2[17];
snprintf(line2, 17, "Outside zone %dm", (int)dist);
lcdShow("MODE: GPS", line2);
}
}
// -------------------- RELAY CONTROL HELPERS --------------------
void setHighBeam() {
// HIGH beam ON -> activate relayHighPin, deactivate relayLowPin
digitalWrite(relayLowPin, LOW);
digitalWrite(relayHighPin, HIGH);
}
void setLowBeam() {
// LOW beam ON -> activate relayLowPin, deactivate relayHighPin
digitalWrite(relayHighPin, LOW);
digitalWrite(relayLowPin, HIGH);
}
// -------------------- LCD helpers --------------------
void lcdShow(const char* line1, const char* line2) {
lcd.clear();
lcd.setCursor(0,0);
lcd.print(line1);
lcd.setCursor(0,1);
lcd.print(line2);
}
void lcdMultipleModes() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("MULTIPLE MODES!");
lcd.setCursor(0,1);
lcd.print("Beam: LOW (safe)");
}
// -------------------- Utility: haversine-like distance (meters) --------------------
// double radians(double deg) {
// return deg * 3.14159265358979323846 / 180.0;
// }
// double distanceMeters(double lat1, double lon1, double lat2, double lon2) {
// // approximate haversine formula
// const double R = 6371000.0; // Earth radius in meters
// double dLat = radians(lat2 - lat1);
// double dLon = radians(lon2 - lon1);
// double a = sin(dLat/2.0)*sin(dLat/2.0) + cos(radians(lat1)) * cos(radians(lat2)) * sin(dLon/2.0)*sin(dLon/2.0);
// double c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
// return R * c;
// }
// -------------------- Utility: haversine-like distance (meters) --------------------
double toRadians(double deg) {
return deg * 3.14159265358979323846 / 180.0;
}
double distanceMeters(double lat1, double lon1, double lat2, double lon2) {
// approximate haversine formula
const double R = 6371000.0; // Earth radius in meters
double dLat = toRadians(lat2 - lat1);
double dLon = toRadians(lon2 - lon1);
double a = sin(dLat/2.0)*sin(dLat/2.0)
+ cos(toRadians(lat1)) * cos(toRadians(lat2))
* sin(dLon/2.0)*sin(dLon/2.0);
double c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
return R * c;
}
Below I’ve given a complete, ready-to-upload Arduino sketch plus a detailed, line-by-line hardware wiring and pin-description section. The code implements:
-
3 mode toggles (Manual / Auto (LDR) / GPS).
-
Manual mode: driver selects High/Low beam with a separate manual-beam toggle.
-
Auto (LDR) mode: LDR digital output switches beams automatically.
-
GPS mode: when inside a user-configured geofence (lat/lon + radius) beams force LOW.
-
If more than one mode toggle is ON at the same time → LCD shows "MULTIPLE MODES" and beam stays LOW.
I explain each connection and give alternatives (pull-ups/pull-downs), power notes, and how to set the GPS geofence.
1) Summary of pins (reference for wiring)
(Using Arduino UNO)
-
D2 — LDR digital output (DO) (from LDR module)
-
D3 — Manual Beam toggle switch input (driver chooses high/low)
-
D4 — Mode toggle: MANUAL (ON = manual mode selected)
-
D5 — Mode toggle: AUTO_LDR (ON = automatic LDR mode selected)
-
D6 — Mode toggle: GPS_MODE (ON = GPS mode selected)
-
D8 — Relay control for HIGH beam (Relay IN1)
-
D9 — Relay control for LOW beam (Relay IN2)
-
A4 — I2C SDA (LCD)
-
A5 — I2C SCL (LCD)
-
D10 — GPS RX (connect to GPS module TX)
-
D11 — GPS TX (connect to GPS module RX) — used by SoftwareSerial
Power / common:
-
5V and GND — supply to Arduino, LDR module VCC, relay VCC (if relay module is 5V)
-
Bike 12V — to relay COM terminals (not to Arduino). Use a fuse on the +12V feed.
2) Wiring — step-by-step (textual diagram)
Important safety: disconnect battery while wiring. Use appropriate inline fuse on the bike +12V line (e.g., 1–2 A for headlight feed, check headlight rating). Always common ground the Arduino and the relay module (and GPS/LDR modules) to the bike ground if using a buck converter.
Modules & components
-
Arduino UNO / Nano
-
2-channel relay module (5V)
-
LDR module (3-pin: VCC, GND, DO)
-
I2C LCD (e.g., 16x2 with PCF8574 backpack) — SDA/A4, SCL/A5
-
GPS module (e.g., NEO-6M) — TX/RX pins
-
4 toggle switches:
-
3 mode selection toggles: ManualMode, AutoMode (LDR), GPSMode.
-
1 manual-beam toggle (High/Low) used when ManualMode is active.
-
Wiring, common ground, buck converter (12V→5V) if powering Arduino from bike 12V.
Wiring map (detailed)
Arduino ←→ LDR module
-
LDR VCC → Arduino 5V
-
LDR GND → Arduino GND
-
LDR DO → Arduino D2
Arduino ←→ Relay module
-
Relay VCC → Arduino 5V (or 5V regulator output)
-
Relay GND → Arduino GND
-
Relay IN1 → Arduino D8 (controls HIGH beam relay)
-
Relay IN2 → Arduino D9 (controls LOW beam relay)
Relay ←→ Bike headlight
-
Relay1 COM → +12V battery (through fuse)
-
Relay1 NO → Headlight HIGH beam wire (e.g., white). When relay ON, COM→NO supplies +12V to HIGH beam.
-
Relay2 COM → +12V battery (through fuse)
-
Relay2 NO → Headlight LOW beam wire (e.g., blue/yellow).
-
Headlight ground wire → Bike chassis ground (do not run headlight ground through relays).
Arduino ←→ I2C LCD
-
SDA → A4
-
SCL → A5
-
LCD VCC → 5V
-
LCD GND → GND
Arduino ←→ GPS module (SoftwareSerial)
-
GPS TX → Arduino D10 (GPS TX → SoftwareSerial RX)
-
GPS RX → Arduino D11 (GPS RX ← SoftwareSerial TX)
-
GPS VCC → 5V (or module-specific voltage)
-
GPS GND → GND
Mode toggle switches wiring (recommended using internal pull-ups)
-
One side of each toggle switch → Arduino input pin (D4, D5, D6 for mode toggles; D3 for manual-beam toggle).
-
Other side of each toggle → Arduino GND.
-
In code we use INPUT_PULLUP so the pin reads HIGH when switch is open (OFF), and LOW when switch is closed (ON). That simplifies wiring (no external resistors).
So wiring summary for toggles:
-
ManualMode toggle: one end → D4, other end → GND
-
Auto(LDR) toggle: one end → D5, other → GND
-
GPSMode toggle: one end → D6, other → GND
-
Manual Beam toggle (High/Low): one end → D3, other → GND
(If you prefer active HIGH toggles, wire to 5V and use external pull-down resistors or enable INPUT + externals — but INPUT_PULLUP is easiest.)
3) Behavior details & rules (clarify logic)
-
Mutual exclusivity rule: If more than one mode toggle is ON at the same time → the system shows "MULTIPLE MODES" on the LCD and sets beam to LOW (safe default).
-
Manual Mode ON alone: use Manual Beam toggle (D3) to set HIGH or LOW.
-
Auto LDR Mode ON alone: read LDR DO — when DO == LOW (bright incoming light) => set LOW beam; when DO == HIGH => set HIGH beam (same as your original logic).
-
GPS Mode ON alone: if GPS position is valid AND within user-configured radius of the defined geofence center => force LOW beam; else allow HIGH beam. (This lets you force low beam in certain areas like city limits; set lat/lon/radius in code.)
-
No mode selected (all OFF): default safe behavior — LOW beam ON.
-
LCD shows current mode and beam state and error messages (GPS no fix, multiple modes, etc).
No comments:
Post a Comment