# Building a Custom Meshtastic Node with ESP32 + SX1262 (DIY Guide)

I want this guide to constantly evolve. If you want to suggest changes I could make I have uploaded the markdown file from which this blog post is being generated here: <https://cdn.merlin-glander.de/posts/assets/cheap-meshtastic-node.md> For changes you want to suggest, email me here: [merlin@merlin-glander.de](mailto:merlin@merlin-glander.de)

This guide documents how to build a **fully custom Meshtastic node** using a generic **ESP32 DevKit** and an **SX1262 LoRa module**, powered from a **single 18650 battery** with proper charging, protection, and voltage regulation.

The focus of this guide is **doing it the correct way**:

* No runtime pin hacks
* No trial-and-error wiring
* No prebuilt Meshtastic boards
* Fully reproducible and explainable

If you are comfortable with soldering and the terminal, this guide is for you.

---

## Why Build a Custom Node?

Building your own Meshtastic node gives you:

* Full control over hardware and power design
* Better understanding of how Meshtastic actually works
* Flexibility to reuse parts you already have
* No vendor lock-in
* Easier debugging and modification later

This setup is electrically equivalent to many commercial nodes — just without the markup.

---

## Important Concepts (Read This First)

### Radio pins are compile-time only

Meshtastic **does not allow changing LoRa radio pins at runtime** via the CLI or the mobile app.

All radio pin assignments:

* Are defined in firmware
* Must be compiled in
* Require a rebuild if you change wiring

If you try to set radio pins using the CLI, it will **not work**.

---

### SX1262 is not “just SPI”

Unlike older SX127x radios, the SX1262 **requires additional control signals**:

* `BUSY` — mandatory
* `DIO1` — mandatory (interrupt/IRQ)
* `NRST` — strongly recommended

If any of these are missing or miswired, the radio will fail with:

```
SX126x init result -2
```

This error almost always means **wiring**, not software.

---

## Hardware Used

### Core electronics

* ESP32 DevKit (ESP32-WROOM-32, e.g. `esp32doit-devkit-v1`)
* SX1262 LoRa module (RA-62 / HT-RA62 / LLCC68-compatible)
* 18650 Li-ion battery

### Power system

* TP4056 **with battery protection**
* MT3608 DC-DC boost converter

### Misc

* USB cable (flashing/debugging)
* Jumper wires or soldered connections
* Antenna appropriate for your region (EU868 / US915, etc.)

⚠️ **SX1262 is 3.3 V only. Never connect it to 5 V.**

---

## Power System Design (Battery + Charging)

This build uses a **safe and robust battery system** suitable for unattended operation.

### Components and roles

#### TP4056 (with protection)

* Charges a single 18650 cell via USB
* Includes:

  * Over-charge protection
  * Over-discharge protection
  * Short-circuit protection
* Battery connects to `B+ / B-`
* Load is taken from `OUT+ / OUT-`

#### MT3608 boost converter

* Boosts battery voltage (≈3.0–4.2 V)
* Output is adjusted to **5.0 V**
* Feeds the ESP32 via its **VIN / VN** pin

---

### Power wiring overview

```
18650 Battery
   │
   ▼
TP4056 (with protection)
   ├─ B+ / B- → Battery
   └─ OUT+ / OUT- → MT3608 input
                      │
                      ▼
               MT3608 (set to 5.0 V)
                      │
                      ▼
              ESP32 VIN / VN
```

### Why this works well

* ESP32 receives stable voltage even as battery discharges
* Battery is protected from damage
* Charging and load paths are properly isolated
* SX1262 is powered from ESP32’s onboard 3.3 V regulator

---

## SX1262 ↔ ESP32 Pin Assignment

This is the **exact pin mapping used in the working firmware**.

| SX1262 Pin | ESP32 GPIO | Purpose                  |
| ---------- | ---------- | ------------------------ |
| VCC        | 3V3        | Radio power              |
| GND        | GND        | Common ground            |
| NSS (CS)   | GPIO5      | SPI chip select          |
| SCK        | GPIO18     | SPI clock                |
| MOSI       | GPIO23     | SPI data (ESP32 → radio) |
| MISO       | GPIO19     | SPI data (radio → ESP32) |
| NRST       | GPIO14     | Radio reset              |
| DIO1       | GPIO26     | Radio interrupt (IRQ)    |
| BUSY       | GPIO25     | Radio busy indicator     |
| DIO2       | —          | Not used                 |
| DIO3       | —          | Not used                 |
| TXEN       | —          | Not used                 |
| RXEN       | —          | Not used                 |

---

## Wiring Checklist

Before powering on:

* MT3608 output verified at **5.0 V**
* ESP32 powered via **VIN / VN**
* SX1262 powered from **3V3**
* NSS → GPIO5
* BUSY → GPIO25
* DIO1 → GPIO26
* NRST → GPIO14
* All grounds connected
* Antenna attached

Most SX1262 problems are wiring mistakes — triple-check this.

---

## Getting the Meshtastic Firmware

Meshtastic uses **PlatformIO**, not the Arduino IDE.

```bash
git clone https://github.com/meshtastic/firmware.git
cd firmware
git checkout develop
```

---

## Creating a Custom Variant

Meshtastic organizes board definitions as **variants**.

Create a new variant directory:

```bash
cd variants
mkdir esp32_devkit_sx1262
```

Directory structure:

```
variants/esp32_devkit_sx1262/
├── platformio.ini
└── variant.h
```

---

## `platformio.ini` (Exact Working Configuration)

```ini
[env:esp32_devkit_sx1262]
extends = esp32_base
board = esp32doit-devkit-v1

build_flags =
  ${esp32_base.build_flags}
  -D PRIVATE_HW
  -I variants/esp32_devkit_sx1262

lib_deps =
  ${esp32_base.lib_deps}
```

### What this does

* Uses Meshtastic’s ESP32 defaults
* Targets a generic ESP32 DevKit
* Marks this as private/custom hardware
* Ensures your variant header is included

---

## `variant.h` (Exact Working Configuration)

```c
#pragma once

// Minimal custom variant for ESP32 DevKit + external SX1262 module

// --- Radio selection ---
#define USE_SX1262

// --- SX1262 control pins ---
#define SX126X_CS    5    // NSS / CS
#define SX126X_RESET 14   // NRST
#define SX126X_BUSY  25   // BUSY
#define SX126X_DIO1  26   // DIO1 (IRQ)

// --- SPI bus (ESP32 VSPI) ---
#define LORA_SCK  18
#define LORA_MOSI 23
#define LORA_MISO 19

// Compatibility with older Meshtastic code paths
#define LORA_CS   SX126X_CS
#define LORA_DIO1 SX126X_DIO1

// Maximum safe output power for most SX1262 modules
#define SX126X_MAX_POWER 22

// Enable these ONLY if your module explicitly requires them
// #define SX126X_DIO3_TCXO_VOLTAGE 1.8
// #define SX126X_DIO2_AS_RF_SWITCH
```

---

## Building the Firmware

From the firmware root:

```bash
pio run -e esp32_devkit_sx1262
```

If the build complains about `mklittlefs`, install a prebuilt `mklittlefs` binary and ensure it is available in your `PATH`.

---

## Flashing the ESP32

```bash
pio run \
  -e esp32_devkit_sx1262 \
  -t upload \
  --upload-port /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
```

A successful flash ends with:

```
Hash of data verified.
Hard resetting via RTS pin...
[SUCCESS]
```

---

## Verifying Radio Initialization

Open a serial monitor:

```bash
picocom -b 115200 /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0
```

Expected output:

```
SX126xInterface(cs=5, irq=26, rst=14, busy=25)
SX126x init result 0
```

If you see `init result -2`, recheck wiring.

---

## Connecting to Meshtastic

### Serial

```bash
meshtastic --port /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 --info
```

### Bluetooth

* Open the Meshtastic mobile app
* Connect via BLE to `Meshtastic_XXXX`

---

## Setting the Regulatory Region

Always set the correct region:

```bash
meshtastic --port /dev/serial/... --set lora.region EU_868
```

---

## Common Pitfalls

* Confusing GPIO numbers with board pin labels
* Leaving BUSY unconnected
* Powering SX1262 from 5 V
* Feeding raw battery voltage into VIN
* Expecting radio pin configuration via CLI
* Forgetting the antenna

---

## Final Thoughts

This build gives you:

* A safe battery-powered Meshtastic node
* Full hardware control
* A clean, maintainable firmware setup
* Zero dependence on proprietary boards

Once the SX1262 initializes correctly, Meshtastic is extremely stable and reliable.

Happy building 🛰️

