When routing Arduino wiring for mixed-signal projects—specifically combining sensitive analog sensors with high-current inductive loads like DC motors—the most common point of failure isn't the code; it's the power topology. The direct answer for reliable dual-axis motor control is to physically separate your logic ground and motor ground, tying them together at exactly one star-ground point, while powering the motor driver from an independent external power supply. This guide walks through the exact wiring, component selection, and debugging sequence for a dual-axis solar tracker using Light Dependent Resistors (LDRs) and an L298N H-bridge.
The Core Arduino Wiring Decision: Logic vs. Motor Power
Before cutting a single wire, you must decide how to power your motor driver. The Arduino Uno's onboard 5V regulator maxes out around 800mA (and realistically, you should keep it under 400mA to avoid thermal shutdown). Attempting to pull motor current through the Uno's VIN or 5V pins will brownout the ATmega328P, causing erratic behavior or permanent regulator damage.
| Condition | Decision Path | Concrete Pick |
|---|---|---|
| Motor stall current > 3A | L298N will overheat and drop 2V+ across its BJT transistors. You need a MOSFET-based driver. | BTS7960 (43A max) |
| Motor stall current 0.5A - 2.5A, Bidirectional | Standard BJT H-bridge is acceptable. Requires external PSU, common ground with Uno. | L298N Dual H-Bridge (Default Pick) |
| Motor stall current < 0.5A, Unidirectional | H-bridge is overkill. Use a single logic-level N-channel MOSFET with a flyback diode. | IRLZ44N MOSFET |
| Motor is a Servo (< 1A peak) | Servos have internal controllers. Wire directly to external 5V/6V BEC, signal pin to Uno PWM. | PCA9685 or direct PWM |
For this build, we terminate the decision path on the L298N Dual H-Bridge driven by TT gear motors (approx. 150mA run, 800mA stall), powered by an external 12V supply.
Parts List & Spec Sheet
This build relies on specific, widely available variants. Substituting the LDRs or the motor driver will require recalibrating the analog thresholds in the code.
| Component | Exact Variant / Model | Key Spec | Est. Cost (2026) |
|---|---|---|---|
| Microcontroller | Arduino Uno R3 (ATmega328P DIP) | 14 digital I/O, 6 analog inputs (10-bit ADC) | $24.00 |
| Motor Driver | L298N Dual H-Bridge Module (STMicroelectronics chip) | 2A max per channel, 2V saturation drop | $6.50 |
| Light Sensors | GL5528 LDR (4-pack) | 10-20kΩ at 10 lux, 2-4kΩ at 100 lux | $3.00 |
| Motors | TT Gear Motor (2-pack, 1:48 ratio) | 3V-6V nominal, 200 RPM no-load | $5.00 |
| Power Supply | 12V 2A DC Switching PSU (Barrel jack to screw terminal) | 24W total capacity | $9.00 |
| Passives | 10kΩ Resistors (1/4W, 5% tolerance) | Voltage divider pull-downs for LDRs | $1.00 |
Pin Mapping & Physical Wiring Steps
Correct Arduino wiring requires strict adherence to pin capabilities. The L298N direction pins (IN1-IN4) can use standard digital I/O, but the enable pins (ENA, ENB) must be connected to hardware PWM-capable pins on the Uno to allow speed control via analogWrite().
| Arduino Uno Pin | Destination | Wire Color (Recommended) | Function |
|---|---|---|---|
| 5V | Breadboard VCC Rail | Red | Logic power for LDR voltage dividers |
| GND | Breadboard GND Rail & L298N GND | Black | Star-ground reference |
| D5 (PWM) | L298N ENA | Blue | PWM speed control for Axis 1 (Left/Right) |
| D6 (PWM) | L298N ENB | Blue | PWM speed control for Axis 2 (Up/Down) |
| D7 | L298N IN1 | Green | Axis 1 Direction A |
| D8 | L298N IN2 | Green | Axis 1 Direction B |
| D9 | L298N IN3 | Green | Axis 2 Direction A |
| D10 | L298N IN4 | Green | Axis 2 Direction B |
| A0 | LDR 1 (Top-Left) | Yellow | Analog input (0-1023) |
| A1 | LDR 2 (Top-Right) | Yellow | Analog input (0-1023) |
| A2 | LDR 3 (Bottom-Left) | Yellow | Analog input (0-1023) |
| A3 | LDR 4 (Bottom-Right) | Yellow | Analog input (0-1023) |
Numbered Wiring Procedure
- Establish the Star Ground: Run a black wire from the Uno GND pin to the breadboard ground rail. Run a second black wire from that same breadboard rail to the L298N GND terminal. Finally, connect the 12V PSU negative terminal to the L298N GND terminal. Do not daisy-chain grounds; tie them all to the L298N ground screw terminal.
- Wire Motor Power: Connect the 12V PSU positive terminal to the L298N 12V input terminal. Leave the 5V-EN jumper on the L298N removed (this disables the onboard 5V regulator, preventing backfeed into the Uno).
- Build LDR Voltage Dividers: For each of the four GL5528 LDRs, connect one leg to the breadboard 5V rail. Connect the other leg to a 10kΩ resistor. Connect the opposite end of the 10kΩ resistor to the breadboard GND rail. The junction between the LDR and the 10kΩ resistor goes to the Uno analog pins (A0-A3). As light increases, LDR resistance drops, raising the voltage at the analog pin.
- Connect Logic and PWM: Wire the digital pins (D7-D10) to IN1-IN4. Wire D5 and D6 to ENA and ENB. Ensure the jumper caps on ENA and ENB are removed, otherwise the motors will run at full speed and ignore your PWM signals.
Compilable Code with Hardware Error Handling
This code targets the Arduino Uno R3 (ATmega328P). It includes a hardware diagnostic routine that runs on startup and continuously monitors the analog lines for physical wiring faults (shorts to ground or open circuits). According to the official Arduino analogRead() documentation, a floating pin will yield erratic values, which we trap here.
// Target Board: Arduino Uno R3 (ATmega328P)
// Dual-Axis Tracker with L298N and GL5528 LDRs
// --- PIN DEFINITIONS ---
#define ENA 5 // PWM Axis 1
#define ENB 6 // PWM Axis 2
#define IN1 7 // Dir Axis 1
#define IN2 8 // Dir Axis 1
#define IN3 9 // Dir Axis 2
#define IN4 10 // Dir Axis 2
#define LDR_TL A0 // Top Left
#define LDR_TR A1 // Top Right
#define LDR_BL A2 // Bottom Left
#define LDR_BR A3 // Bottom Right
// --- THRESHOLDS ---
#define TOLERANCE 40 // Deadzone to prevent motor jitter
#define MOTOR_SPEED 180 // PWM value (0-255)
#define ERR_LOW 15 // Threshold for short-to-ground
#define ERR_HIGH 1010 // Threshold for open-circuit/short-to-VCC
bool hardwareFault = false;
void setup() {
Serial.begin(115200);
// Initialize Motor Pins
pinMode(ENA, OUTPUT);
pinMode(ENB, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
// Stop motors initially
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
analogWrite(ENA, 0); analogWrite(ENB, 0);
Serial.println("System Boot: Running Wiring Diagnostics...");
checkSensorHealth();
}
void loop() {
if (hardwareFault) {
haltMotors();
delay(2000); // Prevent serial spam
return;
}
int tl = analogRead(LDR_TL);
int tr = analogRead(LDR_TR);
int bl = analogRead(LDR_BL);
int br = analogRead(LDR_BR);
// Calculate averages for X and Y axes
int avgTop = (tl + tr) / 2;
int avgBot = (bl + br) / 2;
int avgLeft = (tl + bl) / 2;
int avgRight = (tr + br) / 2;
// Axis 1 (Left/Right) - Horizontal tracking
int diffX = avgRight - avgLeft;
if (abs(diffX) > TOLERANCE) {
if (diffX > 0) moveAxis1(1);
else moveAxis1(-1);
} else {
stopAxis1();
}
// Axis 2 (Up/Down) - Vertical tracking
int diffY = avgTop - avgBot;
if (abs(diffY) > TOLERANCE) {
if (diffY > 0) moveAxis2(1);
else moveAxis2(-1);
} else {
stopAxis2();
}
delay(150); // Mechanical settling time
}
// --- HARDWARE ERROR HANDLING ---
void checkSensorHealth() {
int pins[] = {LDR_TL, LDR_TR, LDR_BL, LDR_BR};
String names[] = {"TL", "TR", "BL", "BR"};
for (int i = 0; i < 4; i++) {
int val = analogRead(pins[i]);
if (val < ERR_LOW) {
Serial.print("ERR: LDR_SHORT_TO_GND_CH");
Serial.println(names[i]);
hardwareFault = true;
} else if (val > ERR_HIGH) {
Serial.print("ERR: LDR_OPEN_CIRCUIT_CH");
Serial.println(names[i]);
hardwareFault = true;
}
}
if (!hardwareFault) {
Serial.println("Diagnostics PASSED. All LDRs within nominal voltage divider range.");
} else {
Serial.println("HALT: Fix physical wiring faults before operating motors.");
}
}
// --- MOTOR CONTROL FUNCTIONS ---
void moveAxis1(int dir) {
analogWrite(ENA, MOTOR_SPEED);
digitalWrite(IN1, dir > 0 ? HIGH : LOW);
digitalWrite(IN2, dir > 0 ? LOW : HIGH);
}
void stopAxis1() {
analogWrite(ENA, 0);
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
}
void moveAxis2(int dir) {
analogWrite(ENB, MOTOR_SPEED);
digitalWrite(IN3, dir > 0 ? HIGH : LOW);
digitalWrite(IN4, dir > 0 ? LOW : HIGH);
}
void stopAxis2() {
analogWrite(ENB, 0);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
void haltMotors() {
stopAxis1();
stopAxis2();
}
Debugging: First Three Checks & Exact Error Strings
First Three Things to Check:
- Common Ground: Measure resistance between the Uno GND pin and the L298N GND screw terminal with a multimeter. It must read < 1.0 Ω. If it's higher, your logic signals are floating relative to the motor driver.
- ENA/ENB Jumpers: Visually verify the brass jumper caps on the L298N ENA and ENB pins are physically removed. If they are installed, the H-bridge is hardwired to 5V logic HIGH, overriding your PWM signals and locking the motor at 100% duty cycle.
- Voltage Drop under Load: Measure the 12V PSU terminals while the motors are stalled. The STMicroelectronics L298N datasheet specifies a typical saturation voltage drop of 2V across the internal Darlington pairs. If your PSU sags below 9V under load, the logic threshold fails.
Ranked Causes for Serial Monitor Errors
If your serial output halts with an error string, follow this decision path:
- Error String:
ERR: LDR_SHORT_TO_GND_CH0- Cause 1 (Most Likely): The analog wire from A0 is touching the breadboard ground rail, or the LDR leg is shorted.
- Cause 2: The 10kΩ pull-down resistor is missing, and the LDR is wired directly to GND.
- Error String:
ERR: LDR_OPEN_CIRCUIT_CH1- Cause 1 (Most Likely): The 5V VCC wire to the LDR voltage divider is disconnected. The analog pin is floating and picking up ambient EMI, maxing out the 10-bit ADC at 1023.
- Cause 2: The breadboard contact for the 10kΩ resistor is bent or not making contact.
Extending or Simplifying the Build
Once the baseline Arduino wiring is proven stable, you have two distinct paths to modify the system based on your end goal.
To Simplify (Cost & Part Reduction):
Drop the L298N and TT motors entirely. Switch to two SG90 micro servos. Servos contain their own internal H-bridge and potentiometer feedback. You will wire the servo VCC to an external 5V 2A BEC (Battery Eliminator Circuit), tie the BEC GND to the Uno GND, and wire the servo signal wires directly to Uno pins D5 and D6. You will replace the analogWrite() motor logic with the standard Servo.h library's write() function, mapping your LDR differential directly to a 0-180 degree angle.
To Extend (Precision & Logging):
Add an INA219 I2C current/voltage sensor (Adafruit product ID 904) to monitor the actual power draw of the motors. Wire the INA219 SDA/SCL to the Uno's A4/A5 pins (with 4.7kΩ pull-up resistors to 5V). By reading the shunt voltage via I2C, you can implement a software current limit in the loop(). If the INA219 reports current > 1.5A (indicating a mechanical jam or stall), the code can immediately cut PWM to the L298N, preventing the H-bridge from melting. This adds roughly $10 to the BOM but transitions the project from a hobby toy to a robust, fault-tolerant embedded system.






