LoRa Best Practices (SX1276 / SX1278 + ESP32)¶
LoRa is a physical-layer radio modulation that enables long-range, low-power wireless links. It is not a network protocol β it is a radio waveform. Understanding this distinction prevents most of the design mistakes that make LoRa deployments unreliable or legally non-compliant.
1. LoRa vs LoRaWAN¶
| LoRa | LoRaWAN | |
|---|---|---|
| What it is | Radio modulation (PHY layer) | Network stack on top of LoRa |
| Topology | Point-to-point or star (you define) | Gateway β Network Server β App Server |
| Encryption | None by default | AES-128 built in |
| Suitable for | Two ESP32s talking to each other | Connecting to The Things Network |
| Complexity | Low | Higher |
This document covers raw LoRa β direct SX127x chip communication between two or more nodes, without a LoRaWAN network server.
Duty cycle applies to both: LoRa and LoRaWAN transmissions occupy shared ISM spectrum. Regulatory duty cycle limits apply regardless of whether you use a network stack.
2. Radio Fundamentals¶
Spreading Factor (SF)¶
SF7 through SF12. Each step up doubles the airtime and increases sensitivity by ~2.5 dB.
| SF | Approx. range | Airtime (10 byte payload) | Receiver sensitivity |
|---|---|---|---|
| SF7 | Shortβmedium | ~50 ms | β123 dBm |
| SF9 | Medium | ~200 ms | β129 dBm |
| SF12 | Long | ~1.5 s | β137 dBm |
Rule: use the lowest SF that achieves reliable link. High SF monopolizes airtime and burns battery.
Bandwidth¶
Standard options: 125 kHz, 250 kHz, 500 kHz.
- Narrower bandwidth β better sensitivity, longer airtime
- Wider bandwidth β shorter airtime, less range
- 125 kHz is the standard choice for most IoT applications
Coding Rate (CR)¶
CR⅘ through CR4/8. Higher CR adds more forward error correction, increasing resilience at the cost of more airtime. CR⅘ is the most common default.
Airtime Budget¶
Transmit time = (payload size + header) Γ SF Γ coding rate / bandwidth
For a 20-byte payload at SF9, 125 kHz, CR⅘: approximately 370 ms.
At 1% duty cycle (EU 868 MHz): maximum 1 transmission per ~37 seconds for this configuration. Design your polling interval accordingly.
3. Duty Cycle Regulations¶
Regulatory compliance
This is engineering guidance, not legal advice. Verify your local regulatory requirements before deploying radio equipment.
EU 868 MHz band (LoRa's primary frequency in Europe): - Sub-band 868.0β868.6 MHz: 1% duty cycle (36 s silence per 3.6 s transmission) - Sub-band 868.7β869.2 MHz: 0.1% duty cycle - Sub-band 869.4β869.65 MHz: 10% duty cycle (used for downlinks) - Devices that exceed duty cycle limits are illegal to operate and may interfere with emergency services
US 915 MHz (FCC Part 15): - Frequency hopping required β no fixed-frequency continuous transmission - Uses FHSS across 64 uplink channels - No explicit duty cycle percentage, but effective transmission is limited by the hopping requirement - Most US LoRa modules implement this automatically if configured correctly
Design for compliance: - Track transmit time in firmware; enforce minimum quiet periods - For EU: calculate airtime per transmission, multiply by 100 to get minimum silence period - Do not send heartbeats faster than duty cycle allows
// Duty cycle enforcement (EU 1%)
static uint32_t lastTxMs = 0;
static uint32_t lastAirtimeMs = 0; // measured airtime of last packet
bool canTransmit() {
uint32_t silenceRequired = lastAirtimeMs * 99; // 1% = 1 part per 100
return (millis() - lastTxMs) >= silenceRequired;
}
4. Antenna and RF Layout¶
Always use a proper antenna. Operating a LoRa module without an antenna can damage the PA (power amplifier) in the SX127x. A short circuit or open circuit at the antenna port reflects power back into the chip.
| Antenna type | Gain | Best for |
|---|---|---|
| ¼-wave whip (wire) | ~2 dBi | Short range, prototyping |
| Spring/helical | ~2 dBi | Compact enclosures |
| PCB trace antenna | ~0β2 dBi | Integrated designs |
| External directional | 5β12 dBi | Point-to-point fixed links |
RF layout rules: - Keep the antenna trace and SMA connector away from digital switching signals (SPI clock, PWM, oscillators) - Use a continuous ground plane under the RF section; do not route digital signals through it - Avoid bending the antenna wire at sharp angles β keep it straight or in a gentle arc - On a breadboard, keep the LoRa module as far from the ESP32 as the wires allow β breadboard is lossy RF substrate
Common modules (SX1276-based): - Semtech SX1276 evaluation board - TTGO LoRa32 (ESP32 + SX1276 + OLED on one board) - Heltec WiFi LoRa 32 - RFM95W breakout
5. Power Strategy¶
LoRa TX current is high β 120β130 mA peak at +20 dBm output power. This is a significant spike relative to ESP32 idle current.
LiPo 3.7V
β
Protection IC
β
Buck 3.3V ββ[100Β΅F bulk]ββ[100nF ceramic]ββ VCC rail
β
ESP32 + SX1276
Capacitor placement: 100 Β΅F electrolytic + 100 nF ceramic within 5 mm of the SX1276 VCC pin. TX spikes cause voltage droops on resistive battery leads; capacitors absorb them.
Brownout risk: ESP32 brownout detector triggers at ~2.45 V. During LoRa TX on a weak battery with high-resistance leads, the rail can momentarily dip enough to reset the ESP32. Bulk capacitance and a low-ESR buck converter prevent this.
TX power selection: use the minimum TX power that achieves the required link budget. Each 3 dB reduction halves the current draw.
// RADIOLIB example (use your library's equivalent)
radio.setOutputPower(14); // dBm β not 20 unless range truly demands it
Sleep current: SX1276 sleep mode draws ~1 Β΅A. Always sleep the radio between transmissions.
6. Firmware Strategy¶
Non-blocking send with RadioLib¶
// Async TX β does not block loop()
int state = radio.startTransmit(payload, payloadLen);
// Poll for completion:
if (radio.getIRQFlags() & RADIOLIB_SX127X_IRQ_TX_DONE_MASK) {
radio.clearIRQFlags();
// TX complete β schedule next send
}
State machine TX scheduling¶
enum class RadioState { IDLE, TX_PENDING, TX_ACTIVE, COOLDOWN };
RadioState radioState = RadioState::IDLE;
uint32_t nextTxMs = 0;
uint32_t txStartMs = 0;
void radioTick() {
switch (radioState) {
case RadioState::IDLE:
if (millis() > nextTxMs && dataReady) {
radio.startTransmit(buf, len);
txStartMs = millis();
radioState = RadioState::TX_ACTIVE;
}
break;
case RadioState::TX_ACTIVE:
if (txDone()) {
uint32_t airtime = millis() - txStartMs;
nextTxMs = millis() + airtime * 99; // 1% duty cycle
radioState = RadioState::COOLDOWN;
}
break;
case RadioState::COOLDOWN:
if (millis() > nextTxMs) radioState = RadioState::IDLE;
break;
}
}
Retry logic¶
Do not retry blindly β each retry consumes additional airtime and duty cycle budget. Retry only on explicit NAK or timeout, with exponential backoff:
uint8_t retries = 0;
uint32_t retryDelayMs = 5000;
// On failure:
if (retries < 3) {
retries++;
nextTxMs = millis() + retryDelayMs * retries;
}
7. Security Notes¶
Raw LoRa transmissions are plaintext. Anyone with a compatible receiver within radio range can receive and decode your packets.
Implement application-layer AES encryption for any sensitive payload:
// Example using mbedTLS AES (available on ESP32)
#include <mbedtls/aes.h>
void aesEncrypt(const uint8_t* key, const uint8_t* in, uint8_t* out) {
mbedtls_aes_context ctx;
mbedtls_aes_init(&ctx);
mbedtls_aes_setkey_enc(&ctx, key, 128);
mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, in, out);
mbedtls_aes_free(&ctx);
}
Use CBC mode with a random IV for payloads longer than one block. ECB is shown above only for brevity.
Key management: embed the AES key in NVS (see Embedded Security & OTA). Rotate keys periodically β LoRa range means an attacker can be far away and invisible.
Do not transmit: - GPS coordinates of the device if its location is sensitive - Authentication tokens or passwords - Device identifiers that enable tracking
8. See Also¶
See also
- LoRaWAN vs Raw LoRa (Deep Dive) β analytical comparison of protocol stacks, security models, regulatory implications, and infrastructure trade-offs
- Power Management & Deep Sleep β deep sleep between LoRa transmissions; battery life strategy
- ESP32 Hardware & Electrical Safety β power supply decoupling, GPIO current limits
- Power Electronics for ESP32 β MOSFET power gating of LoRa module, LiPo circuits for remote nodes
- MQTT Security Best Practices β if bridging LoRa packets to MQTT via a gateway ESP32
- Embedded Security & OTA β AES key storage in NVS, OTA for LoRa gateway nodes