Designing Your First Custom Arduino PCB Board: The Nano Carrier Approach

Transitioning from a messy breadboard to a custom Arduino PCB board is the definitive step from hobbyist prototyping to reliable embedded deployment. When designing a board to switch 12V loads—like solenoid valves, DC motors, or high-power LED arrays—the most robust and cost-effective approach is the "carrier board" method. Instead of routing the raw ATmega328P crystal, decoupling capacitors, and USB-to-Serial ICs yourself, you design a custom PCB that accepts an Arduino Nano v3 as a plug-in module.

Difficulty: Intermediate (Requires basic KiCad/Eagle routing and soldering SMD/through-hole)
Time to Build: 4 hours (Design) + 2 weeks (Fab) + 2 hours (Assembly/Code)
Estimated Cost: $25 for 5 PCBs (JLCPCB/PCBWay) + $18 for components

Exact Parts List

To replicate this 12V dual-relay environmental controller, source these exact variants to ensure footprint and electrical compatibility:

  • Microcontroller: Arduino Nano v3 (ATmega328P, 16MHz, 5V logic)
  • Relays: Songle SRD-05VDC-SL-C (5V coil, 10A @ 120VAC/10A @ 24VDC contacts)
  • Optocouplers: PC817 (DIP-4 package, provides galvanic isolation between 5V logic and 12V relay coil drive)
  • Flyback Diodes: 1N4148 (DO-35) across relay coils to suppress inductive kickback
  • Relay Drive Transistors: 2N2222A (NPN BJT, TO-92)
  • Voltage Regulator: LM2596S-5.0 buck converter module (if stepping 12V down to 5V for the Nano's Vin pin)
  • Terminals: 5.08mm pitch 2-pin PCB screw terminal blocks

Pin Mapping and PCB Trace Routing Rules

Proper pin assignment prevents routing bottlenecks and keeps high-current 12V paths away from sensitive analog inputs. Below is the hardwired mapping for the carrier board.

Nano Pin Function PCB Destination Trace Width (1oz Cu)
D2 Relay 1 Control PC817 Opto 1 (via 1kΩ resistor) 10 mil (0.25mm)
D3 Relay 2 Control PC817 Opto 2 (via 1kΩ resistor) 10 mil (0.25mm)
A0 NTC Thermistor Input Voltage divider node (10kΩ pull-up) 10 mil (0.25mm)
D13 Status / Error LED Onboard 0805 LED (via 470Ω resistor) 10 mil (0.25mm)
Vin Raw Voltage In (7-12V) LM2596 5V Buck Output 40 mil (1.0mm)
GND System Ground Common ground plane / Relay return Pour (GND Plane)
N/A (Terminal) 12V Load Power Relay Common (COM) terminals 80 mil (2.0mm) + Tinning
Routing Tip: According to IPC-2221 trace width standards, a 1oz copper trace carrying 5A requires roughly 115 mils of width. Since our 12V loads might pull 3-4A, use an 80 mil trace for the relay COM/NO paths and apply a thick layer of solder (tinning) over the exposed copper to double the current capacity and reduce resistance.

Firmware: Compilable Code with Hardware Error Handling

Target Board Variant: Arduino Nano v3 (ATmega328P). In the Arduino IDE, select Tools > Board > Arduino AVR Boards > Arduino Nano and Processor: ATmega328P. Do not select the "Old Bootloader" option unless you specifically flashed an older Nano clone.

This firmware reads an NTC thermistor on A0 and triggers Relay 1 if the temperature exceeds a threshold. It includes explicit error handling for floating analog pins and disconnected sensors, which are common failures on custom PCBs due to cold solder joints.

#include <Arduino.h>

// --- PIN DEFINITIONS ---
const uint8_t RELAY_1_PIN = 2;
const uint8_t RELAY_2_PIN = 3;
const uint8_t TEMP_SENSOR_PIN = A0;
const uint8_t ERROR_LED_PIN = 13;

// --- SYSTEM CONSTANTS ---
const float TEMP_THRESHOLD_C = 35.0;
const uint16_t ADC_MAX = 1023;
const float SUPPLY_VOLTAGE = 5.0;
const uint16_t SERIES_RESISTOR = 10000; // 10k Ohm pull-up

// NTC Thermistor parameters (10k @ 25C)
const float NOMINAL_RESISTANCE = 10000;
const float NOMINAL_TEMPERATURE = 25.0;
const float B_COEFFICIENT = 3950;

bool systemFault = false;

void setup() {
  Serial.begin(115200);
  pinMode(RELAY_1_PIN, OUTPUT);
  pinMode(RELAY_2_PIN, OUTPUT);
  pinMode(ERROR_LED_PIN, OUTPUT);
  
  // Ensure relays start in the OFF state (Active LOW for most opto-relay modules)
  digitalWrite(RELAY_1_PIN, HIGH); 
  digitalWrite(RELAY_2_PIN, HIGH);
  
  Serial.println(F("Custom Arduino PCB Board - Relay Controller Initialized"));
}

void loop() {
  uint16_t adcRaw = analogRead(TEMP_SENSOR_PIN);
  
  // ERROR HANDLING: Check for disconnected sensor (reads near 0) or short to VCC (reads near 1023)
  if (adcRaw < 15 || adcRaw > 1008) {
    triggerFault("Sensor disconnected or shorted. ADC: " + String(adcRaw));
    delay(2000);
    return;
  }
  
  systemFault = false;
  digitalWrite(ERROR_LED_PIN, LOW);

  // Convert ADC to Resistance
  float resistance = SERIES_RESISTOR * ((float)ADC_MAX / (float)adcRaw - 1.0);
  
  // Steinhart-Hart simplified (Beta parameter equation)
  float steinhart;
  steinhart = resistance / NOMINAL_RESISTANCE;     // (R/Ro)
  steinhart = log(steinhart);                      // ln(R/Ro)
  steinhart /= B_COEFFICIENT;                      // 1/B * ln(R/Ro)
  steinhart += 1.0 / (NOMINAL_TEMPERATURE + 273.15); // + (1/To)
  steinhart = 1.0 / steinhart;                     // Invert
  steinhart -= 273.15;                             // Convert to Celsius

  Serial.print("Temp: "); Serial.print(steinhart); Serial.println(" C");

  // Relay Control Logic
  if (steinhart > TEMP_THRESHOLD_C) {
    digitalWrite(RELAY_1_PIN, LOW); // Activate Relay 1 (Active LOW)
  } else {
    digitalWrite(RELAY_1_PIN, HIGH); // Deactivate
  }

  delay(500);
}

void triggerFault(String errorMsg) {
  systemFault = true;
  Serial.print(F("FAULT: ")); Serial.println(errorMsg);
  
  // Fail-safe: Turn OFF all relays during a sensor fault
  digitalWrite(RELAY_1_PIN, HIGH);
  digitalWrite(RELAY_2_PIN, HIGH);
  
  // Blink error LED
  for(int i=0; i<3; i++) {
    digitalWrite(ERROR_LED_PIN, HIGH);
    delay(150);
    digitalWrite(ERROR_LED_PIN, LOW);
    delay(150);
  }
}

Debugging: "programmer is not responding" on Custom PCBs

When you receive your freshly fabricated Arduino PCB board from the manufacturer, solder the header pins, plug in the Nano, and connect your USB-Serial FTDI adapter, you will likely hit a wall. The most notorious error in custom Arduino PCB design is:

avrdude: stk500_recv(): programmer is not responding
avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00

This exact error string means the IDE is talking to the COM port, but the ATmega328P bootloader is not waking up to receive the hex file. According to the official Arduino troubleshooting documentation, this is rarely a bad chip and almost always a PCB routing or configuration oversight.

The First Three Things to Check When It Fails

  1. The DTR Auto-Reset Circuit (0.1µF Capacitor): If you are using a raw FTDI header (TX, RX, VCC, GND, DTR) instead of plugging directly into the Nano's mini-USB port, your PCB must have a 0.1µF (100nF) ceramic capacitor wired in series between the FTDI DTR pin and the Nano's RESET pin. Without this capacitor, the board will not auto-reset into the bootloader when you click "Upload", resulting in the stk500_recv timeout. Fix: Manually press the reset button on the Nano exactly one second after clicking Upload, or solder a 0.1µF cap to your PCB.
  2. TX/RX Crossover: A classic mistake on custom carrier boards is wiring FTDI TX to Nano TX. The FTDI TX pin must route to the Nano RX (D0) pin, and FTDI RX must route to Nano TX (D1). If you routed them straight through instead of crossing them, the programmer is shouting into the void. Fix: Cut the traces and run jumper wires to swap D0 and D1 at the FTDI header.
  3. Bootloader Presence and IDE Selection: If you sourced cheap Nano clones from AliExpress, they often ship with the "Old Bootloader" (ATmegaBOOT_168). If the IDE is set to the standard ATmega328P bootloader, the baud rate handshake will fail. Fix: Go to Tools > Processor and select "ATmega328P (Old Bootloader)". If that fails, use an ISP programmer (like a USBasp) to burn the standard Nano bootloader via the ICSP header on your PCB.

Extending and Simplifying the Build

Once your base Arduino PCB board is functioning and passing the avrdude upload test, you will inevitably want to modify the hardware for production or field testing.

How to Simplify the Build

If the PC817 optocouplers and 2N2222 transistors feel like overkill for a low-stakes prototype, you can simplify the BOM (Bill of Materials) by using a ULN2803A Darlington transistor array IC. A single ULN2803A (DIP-18 package) replaces up to 8 discrete transistors, flyback diodes, and base resistors. It interfaces directly with the Nano's 5V logic and can sink the 70mA required by the 5V relay coils. This reduces your PCB footprint and drops the component count by roughly 15 parts.

How to Extend the Build

To transform this local relay controller into an IoT node without redesigning the entire board, extend the layout by adding a 2x4 pin header specifically for an ESP-01S or an I2C OLED display.

  • I2C Extension: Route Nano A4 (SDA) and A5 (SCL) to a 4-pin header with 3.3kΩ pull-up resistors to 5V. This allows you to plug in a 0.96" SSD1306 OLED to display the thermistor data locally.
  • Wireless Extension: Add an AMS1117-3.3 voltage regulator to your PCB. Route Nano D10 (TX) and D11 (RX) to an ESP-01S header. You can then use the Arduino's SoftwareSerial library to send AT commands to the ESP-01S, pushing the temperature data to an MQTT broker over WiFi without migrating the core logic away from the Nano.

Frequently Asked Questions About Arduino PCB Boards

Do I need to bake the bootloader onto my custom Arduino PCB board?

If your custom PCB uses an Arduino Nano or Pro Mini as a plug-in module, no. The bootloader is already flashed onto the microcontroller's memory at the factory. However, if you are designing a "standalone" Arduino PCB board where you solder a raw, blank ATmega328P-PU chip directly to the board, yes. A blank chip does not have the Arduino bootloader. You must include a 2x3 ICSP header on your PCB and use an external ISP programmer (like a USBasp or an Arduino Uno configured as ArduinoISP) to flash the bootloader before you can upload code via a USB-Serial adapter.

How thick should the copper traces be for 12V loads on an Arduino PCB board?

Voltage does not dictate trace width; current (Amperage) does. For a 12V system, the trace width depends entirely on how many amps your load will draw. Using the IPC-2221 standard for internal/external layers: a 12V load drawing 1 Amp requires a trace width of roughly 20 mils (0.5mm) on 1oz copper with a 10°C temperature rise. If your 12V load is a 5 Amp solenoid, you need at least 115 mils (2.9mm) on 1oz copper. Always use a PCB trace width calculator and add a 20% safety margin. For high-current 12V paths, use a 2oz copper pour and leave the traces unmasked so you can flow thick solder over them.

Can I use an ESP32 instead of a Nano for this Arduino PCB board layout?

You can, but you cannot simply drop an ESP32 into an Arduino Nano footprint. The Nano operates at 5V logic, while the ESP32 operates strictly at 3.3V logic. If you route 5V from the PC817 optocouplers or the analog voltage divider directly into an ESP32 GPIO pin, you will permanently fry the ESP32's silicon. To adapt this PCB for an ESP32 (like the ESP32-WROOM-32 DevKit), you must add a logic level shifter (like the BSS138 MOSFET bi-directional shifter) between the 5V relay drive circuitry and the 3.3V ESP32 GPIO pins, and ensure your voltage divider for the thermistor references 3.3V, not 5V.