#include <Servo.h>
// === Tweakable Light Intensity Thresholds (LDR values are inverse to light intensity) ===
// Adjust these values based on your LDR's behavior and desired sensitivity.
const int HIGH_INTENSITY_LDR_THRESHOLD = 400; // LDR values BELOW this mean high light (pupil constricts)
const int LOW_INTENSITY_LDR_THRESHOLD = 600; // LDR values ABOVE this mean low light (pupil dilates)
// Ambient range: LDR values between HIGH_INTENSITY_LDR_THRESHOLD and LOW_INTENSITY_LDR_THRESHOLD (inclusive)
// === LDR Analog Inputs ===
const int ldrLeftPin = A0;
const int ldrRightPin = A1;
// === Servo Outputs ===
const int servoLeftPin = 9;
const int servoRightPin = 10;
// === Reflex Pathway Switches (INPUT_PULLUP: HIGH = OK, LOW = Lesion/Pathology) ===
const int ptnLeftPin = 5; // Pretectal Nucleus Left
const int ptnRightPin = 6; // Pretectal Nucleus Right
const int ewpLeftPin = 7; // Edinger-Westphal Nucleus Left
const int ewpRightPin = 8; // Edinger-Westphal Nucleus Right
const int cn3LeftPin = 3; // 3rd Cranial Nerve Left
const int cn3RightPin = 4; // 3rd Cranial Nerve Right
// === Afferent Lesion Switches (INPUT_PULLUP: HIGH = OK, LOW = Lesion) ===
const int afferentLeftPin = 11; // Left Optic Nerve/Afferent Pathway Lesion
const int afferentRightPin = 12; // Right Optic Nerve/Afferent Pathway Lesion
// === Servo Objects ===
Servo leftIris;
Servo rightIris;
// === Enums for better readability of light levels ===
enum LightLevel {
NO_STIMULUS, // No significant light detected by any intact afferent pathway
LOW_LIGHT, // Dim light detected by at least one intact afferent pathway
AMBIENT_LIGHT, // Ambient light detected by at least one intact afferent pathway
HIGH_LIGHT // High intensity light detected by at least one intact afferent pathway
};
void setup() {
// Attach servos to their respective pins
leftIris.attach(servoLeftPin);
rightIris.attach(servoRightPin);
// Configure all digital switch pins as INPUT_PULLUP.
// This means the pin will read HIGH by default (when switch is open).
// When the switch is closed (connecting pin to ground), it will read LOW.
pinMode(ptnLeftPin, INPUT_PULLUP);
pinMode(ptnRightPin, INPUT_PULLUP);
pinMode(ewpLeftPin, INPUT_PULLUP);
pinMode(ewpRightPin, INPUT_PULLUP);
pinMode(cn3LeftPin, INPUT_PULLUP);
pinMode(cn3RightPin, INPUT_PULLUP);
pinMode(afferentLeftPin, INPUT_PULLUP);
pinMode(afferentRightPin, INPUT_PULLUP);
// Initialize Serial communication for debugging output
Serial.begin(9600);
Serial.println("Light Reflex Model Initialized with Bilateral Response Logic.");
Serial.print("High Intensity LDR Threshold: "); Serial.println(HIGH_INTENSITY_LDR_THRESHOLD);
Serial.print("Low Intensity LDR Threshold: "); Serial.println(LOW_INTENSITY_LDR_THRESHOLD);
Serial.println("Ambient Range: between these thresholds.");
}
void loop() {
// === Read the states of all pathway switches ===
// A 'HIGH' reading indicates the pathway segment is intact (OK).
// A 'LOW' reading indicates a lesion or pathology in that segment.
bool afferentL_OK = digitalRead(afferentLeftPin) == HIGH;
bool afferentR_OK = digitalRead(afferentRightPin) == HIGH;
bool ptnLeft_OK = digitalRead(ptnLeftPin) == HIGH;
bool ptnRight_OK = digitalRead(ptnRightPin) == HIGH;
bool ewpLeft_OK = digitalRead(ewpLeftPin) == HIGH;
bool ewpRight_OK = digitalRead(ewpRightPin) == HIGH;
bool cn3Left_OK = digitalRead(cn3LeftPin) == HIGH;
bool cn3Right_OK = digitalRead(cn3RightPin) == HIGH;
// === Read LDR values to detect light intensity ===
// Lower LDR value indicates higher light intensity.
int leftLDR = analogRead(ldrLeftPin);
int rightLDR = analogRead(ldrRightPin);
// === Assess Afferent Pathway Integrity ===
// The afferent pathway (optic nerve to pretectal nucleus) is intact if both
// the general afferent pathway switch and the specific pretectal nucleus switch are OK.
bool leftAfferentPathwayIntact = afferentL_OK && ptnLeft_OK;
bool rightAfferentPathwayIntact = afferentR_OK && ptnRight_OK;
// === Determine the OVERALL (Global) Light Level sensed by an intact afferent pathway ===
// This dictates the desired pupil response for BOTH eyes, due to bilateral innervation.
LightLevel currentGlobalLightLevel = NO_STIMULUS; // Default to no stimulus (full dilation)
// Priority 1: High Light (If either eye sees high light AND its afferent pathway is intact)
if ((leftLDR < HIGH_INTENSITY_LDR_THRESHOLD && leftAfferentPathwayIntact) ||
(rightLDR < HIGH_INTENSITY_LDR_THRESHOLD && rightAfferentPathwayIntact)) {
currentGlobalLightLevel = HIGH_LIGHT;
}
// Priority 2: Ambient Light (If not high light, but either eye sees ambient light AND its afferent pathway is intact)
else if ((leftLDR >= HIGH_INTENSITY_LDR_THRESHOLD && leftLDR <= LOW_INTENSITY_LDR_THRESHOLD && leftAfferentPathwayIntact) ||
(rightLDR >= HIGH_INTENSITY_LDR_THRESHOLD && rightLDR <= LOW_INTENSITY_LDR_THRESHOLD && rightAfferentPathwayIntact)) {
currentGlobalLightLevel = AMBIENT_LIGHT;
}
// Priority 3: Low Light (If not high or ambient, but either eye sees low light AND its afferent pathway is intact)
else if ((leftLDR > LOW_INTENSITY_LDR_THRESHOLD && leftAfferentPathwayIntact) ||
(rightLDR > LOW_INTENSITY_LDR_THRESHOLD && rightAfferentPathwayIntact)) {
currentGlobalLightLevel = LOW_LIGHT;
}
// If none of the above, it remains NO_STIMULUS, implying no effective light is reaching the pretectal nucleus.
// === Initialize iris angles to default (fully dilated) ===
// 90 degrees for 'angle' maps to 180 on servo (fully open/dilated).
// This is the baseline, overwritten if a reflex response is triggered.
int angleLeft = 90;
int angleRight = 90;
// === Apply reflex logic to iris constriction based on the global light level ===
// LEFT Eye Response
if (ewpLeft_OK && cn3Left_OK) { // Check if left efferent pathway is intact
switch (currentGlobalLightLevel) {
case HIGH_LIGHT:
angleLeft = -90; // High intensity light: Constrict fully (maps to servo 0)
Serial.println(" Left eye: Responding to global HIGH light, constricting.");
break;
case AMBIENT_LIGHT:
angleLeft = 0; // Ambient light: Midline position (maps to servo 90)
Serial.println(" Left eye: Responding to global AMBIENT light, midline.");
break;
case LOW_LIGHT:
angleLeft = 90; // Low intensity light: Dilate fully (maps to servo 180)
Serial.println(" Left eye: Responding to global LOW light, dilating.");
break;
case NO_STIMULUS:
default:
angleLeft = 90; // No effective stimulus or afferent lesion, remains dilated
Serial.println(" Left eye: No global stimulus or afferent lesion, remains dilated.");
break;
}
} else {
Serial.println(" Left eye efferent pathway lesion: remains dilated regardless of light.");
angleLeft = 90; // Lesion present in efferent pathway, stays dilated
}
// RIGHT Eye Response (similar logic)
if (ewpRight_OK && cn3Right_OK) { // Check if right efferent pathway is intact
switch (currentGlobalLightLevel) {
case HIGH_LIGHT:
angleRight = -90; // High intensity light: Constrict fully
Serial.println(" Right eye: Responding to global HIGH light, constricting.");
break;
case AMBIENT_LIGHT:
angleRight = 0; // Ambient light: Midline position
Serial.println(" Right eye: Responding to global AMBIENT light, midline.");
break;
case LOW_LIGHT:
angleRight = 90; // Low intensity light: Dilate fully
Serial.println(" Right eye: Responding to global LOW light, dilating.");
break;
case NO_STIMULUS:
default:
angleRight = 90; // No effective stimulus or afferent lesion, remains dilated
Serial.println(" Right eye: No global stimulus or afferent lesion, remains dilated.");
break;
}
} else {
Serial.println(" Right eye efferent pathway lesion: remains dilated regardless of light.");
angleRight = 90; // Lesion present in efferent pathway, stays dilated
}
// === Apply calculated angles to servos ===
leftIris.write(angleToServo(angleLeft));
rightIris.write(angleToServo(angleRight));
// === Debug Serial Print ===
Serial.print("LDR_L: "); Serial.print(leftLDR);
Serial.print(" | LDR_R: "); Serial.print(rightLDR);
Serial.print(" | Afferent_L_OK: "); Serial.print(afferentL_OK ? "OK" : "Lesion");
Serial.print(" | Afferent_R_OK: "); Serial.print(afferentR_OK ? "OK" : "Lesion");
Serial.print(" | PTN_L_OK: "); Serial.print(ptnLeft_OK ? "OK" : "Lesion");
Serial.print(" | PTN_R_OK: "); Serial.print(ptnRight_OK ? "OK" : "Lesion");
Serial.print(" | EWP_L_OK: "); Serial.print(ewpLeft_OK ? "OK" : "Lesion");
Serial.print(" | EWP_R_OK: "); Serial.print(ewpRight_OK ? "OK" : "Lesion");
Serial.print(" | CN3_L_OK: "); Serial.print(cn3Left_OK ? "OK" : "Lesion");
Serial.print(" | CN3_R_OK: "); Serial.print(cn3Right_OK ? "OK" : "Lesion");
Serial.print(" | Global_Light_Level: ");
if (currentGlobalLightLevel == HIGH_LIGHT) Serial.print("HIGH_LIGHT");
else if (currentGlobalLightLevel == AMBIENT_LIGHT) Serial.print("AMBIENT_LIGHT");
else if (currentGlobalLightLevel == LOW_LIGHT) Serial.print("LOW_LIGHT");
else Serial.print("NO_STIMULUS");
Serial.print(" | Angle_L (deg): "); Serial.print(angleLeft);
Serial.print(" | Angle_R (deg): "); Serial.print(angleRight);
Serial.print(" | Servo_L (0-180): "); Serial.print(angleToServo(angleLeft));
Serial.print(" | Servo_R (0-180): "); Serial.println(angleToServo(angleRight));
delay(150); // Small delay to stabilize readings and avoid flickering
}
// === Helper function to convert conceptual angle (-90 to +90) into servo signal (0-180) ===
// -90 degrees represents full constriction (maps to 0 servo angle)
// 0 degrees represents midline (maps to 90 servo angle)
// +90 degrees represents full dilation (maps to 180 servo angle)
int angleToServo(int angle) {
return map(angle, -90, 90, 0, 180);
}