- C 91.8%
- CMake 3.4%
- Shell 3.2%
- Python 1.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| include | ||
| src | ||
| tools | ||
| .gitignore | ||
| CMakeLists.txt | ||
| conanfile.py | ||
| example.c | ||
| example_rx_zero.c | ||
| pico_sdk_import.cmake | ||
| README.md | ||
lf-rfm95w
Reusable RFM95W / SX1276 LoRa transceiver driver library for the Raspberry Pi Pico C SDK (RP2040 / RP2350 Pico 2).
This library provides complete hardware abstraction for the Semtech SX1276 / HopeRF RFM95W Sub-GHz LoRa transceiver, handling SPI register access, hardware resets, power amplifier control (+20 dBm PA_BOOST with 240 mA OCP), modem parameters, and bidirectional packet transmission and continuous reception.
Part of the Lost + Found suite of hardware drivers.
📚 Citations & Academic References
This driver implementation is built upon the following official specifications and open reference implementations:
- Semtech Corporation: SX1276/77/78/79 Data Sheet Rev 7 (May 2020) - Register mapping, FIFO operations, spreading factor modem configurations, and PA_DAC settings.
- HopeRF: RFM95W/96W/98W Transceiver Module Specification v2.0 - Pinout definitions, frequency bands, and matching requirements.
- Jan Gromes (
jgromes): RadioLib SX127x Architecture (MIT License) - Register sequencing, OCP formulas, and LoRa packet framing.
🔍 Lost + Found LoRa Family: RFM95W (SX1276) vs. SX1262
The Lost + Found suite provides dedicated drivers for two distinct generations of Semtech Sub-GHz LoRa silicon:
| Feature / Architecture | RFM95W (lf-rfm95w) |
SX1262 (lf-sx1262) |
Architecture Notes |
|---|---|---|---|
| Silicon Core | Semtech SX1276 (1st Gen) | Semtech SX1262 (2nd Gen) | RFM95W encapsulates SX1276 with 32MHz crystal in an RF shield |
| Host Protocol | Direct Register Map (0x00–0x70) |
Command / Opcode Interface (0x80, 0x86...) |
SX1276 uses direct 8-bit memory registers; SX1262 uses a coprocessor command set |
| Handshake Line | None (Synchronous SPI) | BUSY Pin (Hardware Handshake) |
Host MCU must monitor BUSY before/after SPI commands on SX1262 |
| Power Efficiency | Linear LDO only (\sim 11\text{ mA} RX) |
Integrated DC-DC Buck (\sim 4.6\text{ mA} RX) |
SX1262 cuts RX active current in half, ideal for solar/battery nodes |
| Max Output Power | +20 dBm (PA_BOOST, 240 mA OCP) | +22 dBm (High-Power PA, 140 mA OCP) | Both deliver maximum legal transmit power |
| Spreading Factors | SF6 – SF12 | SF5 – SF12 | SX1262 adds SF5 for higher bitrates and shorter on-air times |
| Front-End Control | External GPIO / Mode logic | Built-in DIO2 (RF Switch) & DIO3 (TCXO) | SX1262 automates antenna switching & TCXO reference internally |
🔌 Hardware Pin Mappings
Standard Raspberry Pi Pico / Pico 2 (RP2350)
| RFM95W Breakout Pin | Pico 2 GPIO | Physical Pin | Function |
|---|---|---|---|
| 3.3V / VIN | 3V3 (OUT) | Pin 36 | Power supply (3.3V) |
| GND | GND | Pin 18 / 23 / 28 | Common ground reference |
| MISO | GP16 | Pin 21 | SPI0 RX (Data from RFM95W) |
| NSS / CS | GP17 | Pin 22 | SPI0 Chip Select (Active Low) |
| SCK | GP18 | Pin 24 | SPI0 Serial Clock |
| MOSI | GP19 | Pin 25 | SPI0 TX (Data to RFM95W) |
| RESET | GP20 | Pin 26 | Hardware Reset (Active Low) |
| DIO0 | GP22 | Pin 29 | Primary IRQ (TX Done / RX Done) |
| ANA | Antenna | — | 915 MHz Monopole (8.2–8.4 cm wire / SMA) |
Waveshare RP2040-Zero (Right-Edge Consecutive Pins)
| RFM95W Breakout Pin | RP2040-Zero GPIO | Zero Pin Position | Function |
|---|---|---|---|
| 3.3V / VIN | 3V3 | 3V3 | Power supply (3.3V) |
| GND | GND | GND | Common ground reference |
| MISO | GP0 | Pin 0 | SPI0 RX (Data from RFM95W) |
| NSS / CS | GP1 | Pin 1 | SPI0 Chip Select (Active Low) |
| SCK | GP2 | Pin 2 | SPI0 Serial Clock |
| MOSI | GP3 | Pin 3 | SPI0 TX (Data to RFM95W) |
| RESET | GP4 | Pin 4 | Hardware Reset (Active Low) |
| DIO0 | GP5 | Pin 5 | Primary IRQ (TX Done / RX Done) |
| ANA | Antenna | — | 915 MHz Monopole (8.2–8.4 cm wire) |
Note
DIO1,DIO2,DIO3,DIO4, andDIO5can remain unconnected for standard packet operations.
🧠 Why Does the RFM95W Expose 6 DIO Pins? (Legacy FSK/OOK Context)
The RFM95W breakout exposes 6 Digital I/O pins (DIO0 through DIO5). This is due to its dual-personality silicon architecture:
1. Legacy FSK / OOK Streaming
The Semtech SX1276 core is a multi-mode transceiver capable of legacy FSK, GFSK, and OOK modulation (the standards used in garage door openers, 433 MHz weather stations, and older industrial SCADA links). In continuous streaming FSK/OOK modes without packet FIFOs, legacy 8-bit microcontrollers (like AVR / 8051) needed hardware pins for:
- Bit clock recovery (
ClkOut) - Raw data sampling (
DataReady) - Sync address word matches
- Preamble detection
2. LoRa Interrupt Multiplexing
In LoRa mode, the internal state machine maps events across the DIO lines via RegDioMapping1 (0x40) and RegDioMapping2 (0x41):
| Pin | Primary LoRa Mapping | Purpose in LoRa Mode |
|---|---|---|
DIO0 |
RxDone / TxDone |
The Primary Line: Pulses high when a full packet is sent or received. Used by this driver. |
DIO1 |
RxTimeout / CadDetected |
Fires if an expected packet preamble times out or if Channel Activity Detection (CAD) hears RF energy. |
DIO2 |
FhssChangeChannel |
Triggers rapid frequency synthesizer hops in Frequency Hopping Spread Spectrum (FHSS) systems. |
DIO3 |
ValidHeader / PayloadCrcError |
Fires early when the 2.5-byte header arrives, allowing an ultra-low-power MCU to wake up while the payload is in-flight. |
DIO4 |
PllLock / ModeReady |
Hardware indicator that the internal RF PLL synthesizer has locked onto the target frequency. |
DIO5 |
ClkOut |
Outputs an internal divided clock from the 32 MHz oscillator to drive the MCU's clock, saving a crystal on low-cost PCBs. |
3. Why Modern Drivers Only Wire DIO0
On modern 32-bit microcontrollers (like the RP2040 and RP2350), when DIO0 pulses high on RxDone, the MCU reads RegIrqFlags (0x12) over SPI in under 5 microseconds. This instantly retrieves CRC status, header validity, SNR, and RSSI directly from the silicon registers, making DIO1–DIO5 completely optional for standard packet transceivers.
🛠️ Usage with CMake
Add the driver to your Pico project:
add_subdirectory(lib/lf-rfm95w)
target_link_libraries(your_project PRIVATE
lf-rfm95w
)
Transmitter Example
#include "lf_rfm95w.h"
int main() {
stdio_init_all();
lf_rfm95w_t radio;
lf_rfm95w_default_config(&radio);
if (!lf_rfm95w_init(&radio)) {
printf("RFM95W Init Failed!\n");
return -1;
}
lf_rfm95w_lora_config_t config = {
.frequency_hz = 915000000,
.power_dbm = 17,
.spreading_factor = RFM95W_SF_7,
.bandwidth = RFM95W_BW_125_KHZ,
.coding_rate = RFM95W_CR_4_5,
.preamble_length = 8,
.sync_word = RFM95W_SYNC_WORD_PRIVATE,
.crc_on = true
};
lf_rfm95w_configure_lora(&radio, &config);
const char *msg = "LOST+FOUND BEACON #1";
if (lf_rfm95w_transmit_blocking(&radio, (const uint8_t *)msg, strlen(msg), 2000)) {
printf("Sent OK!\n");
}
}
Continuous Receiver Example
#include "lf_rfm95w.h"
int main() {
stdio_init_all();
lf_rfm95w_t radio;
lf_rfm95w_default_config(&radio);
lf_rfm95w_init(&radio);
lf_rfm95w_lora_config_t config = {
.frequency_hz = 915000000,
.power_dbm = 17,
.spreading_factor = RFM95W_SF_7,
.bandwidth = RFM95W_BW_125_KHZ,
.coding_rate = RFM95W_CR_4_5,
.preamble_length = 8,
.sync_word = RFM95W_SYNC_WORD_PRIVATE,
.crc_on = true
};
lf_rfm95w_configure_lora(&radio, &config);
lf_rfm95w_start_receive(&radio);
char rx_buf[128];
while (1) {
lf_rfm95w_rx_status_t status;
uint8_t len = lf_rfm95w_read_packet(&radio, (uint8_t *)rx_buf, sizeof(rx_buf) - 1, &status);
if (len > 0) {
rx_buf[len] = '\0';
printf("Received %d bytes (RSSI: %d dBm, SNR: %d dB, CRC: %s): \"%s\"\n",
len, status.rssi_pkt_dbm, status.snr_pkt_db,
status.crc_error ? "FAIL" : "OK", rx_buf);
}
sleep_ms(10);
}
}
📡 Verified Over-The-Air Telemetry
Synchronized dual-node live capture between RP2040-Zero (Transmitter) and Pico 2 RP2350 (Receiver) on 915.000 MHz:
[RP2040-Zero TX] [TX #39] Broadcasting: "LOST+FOUND ZERO BEACON #39"... Sent OK!
[Pico 2 Receiver] >>> [PACKET RECEIVED #37] 26 bytes | RSSI: -32 dBm | SNR: 6 dB | CRC: OK
[Pico 2 Receiver] Payload: "LOST+FOUND ZERO BEACON #39"
📦 Conan Package Registry
Host & consume via the Lost + Found Conan Package Registry:
conan export .