lora (1.0.0)
Installation
[registries.forgejo]
index = "sparse+ " # Sparse index
# index = " " # Git
[net]
git-fetch-with-cli = truecargo add lora@1.0.0 --registry forgejoAbout this package
lora
Important
The canonical repository for this project lives on Forgejo: https://code.bhk-itsolutions.com/homeiot/lora. This GitHub repository is only a mirror and is not the primary git remote.
no_std Rust driver for LoRa radio modules based on the SX1278 chip. It provides SPI-based control for transmission and reception with configurable operating modes, optional payload compression, and cooperative TX cancellation.
Features
no_std— Suitable for embedded targets without the standard library.- SX1278 support — Register-level driver for frequency, modem config, FIFO, and IRQ handling.
- Operating modes —
Transmitter,Receiver,Transceiver,Manual, andReceiverSingle. - Deep sleep —
deep_sleep()andwake_from_deep_sleep()for minimal chip power consumption (~0.2 µA). - TX/RX — Blocking
send()andreceive()with configurable timeouts; RSSI and SNR in receive results. - Compression — Payload compression/decompression (via the
toolscrate) for smaller over-the-air packets. - Cooperative cancellation — Optional atomic flag to cancel an ongoing transmission from another task or IRQ.
- DIO0 IRQ — Optional binding of an atomic flag for TX_DONE/RX_DONE to integrate with your GPIO interrupt handler.
Cargo features
| Feature | Description |
|---|---|
sx1278 |
Gates the sx1278 module (Controller and its API); lora::error is always available. |
Enable at least sx1278 for normal use:
[dependencies]
# Stable (prod):
lora = { version = "0.4", registry = "forgejo", features = ["sx1278"] }
# Unstable (client staging), after a `dev` publish:
# lora = { version = "0.4", registry = "forgejorc", features = ["sx1278"] }
For publishing or depending from a Forgejo registry, configure forgejo / forgejorc as in this crate’s .cargo/config.toml. Publish / release flow: see .forgejo/README.md.
Usage example
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::SpiBus;
use lora::sx1278::{Controller, Mode};
// SPI, NSS, and RST are your embedded_hal implementations (owned by the controller).
let mut controller = Controller::new(spi, nss, rst, &mut delay, Mode::Transceiver)?;
controller.set_frequency(433_000_000, &mut delay)?; // 433 MHz
// Send a message (blocking until TX_DONE or timeout).
controller.send(&message[..], &mut delay)?;
// Receive into a buffer (blocking until RX_DONE or timeout).
let result = controller.receive(&mut buffer[..], &mut delay)?;
// result.size, result.rssi (dBm), result.snr (dB)
// Put the radio in deep sleep when idle (configuration is retained).
controller.deep_sleep(&mut delay)?;
// send(), receive(), and handle_dio0_irq() wake the chip automatically.
// Call wake_from_deep_sleep() only if you need RX active before the next operation.
controller.wake_from_deep_sleep(&mut delay)?;
Stack and RAM usage
Controller embeds one 2048-byte LZSS window (lzss_window) shared by
send (compression) and receive (decompression):
- Persistent RAM:
Controlleris 2048 bytes larger than a struct without the window. - Transient stack:
sendandreceiveeach keep the codec's LZSS window off the call stack — 2048 bytes less transient stack insendand 1024 bytes less inreceivethan the stack-window codec calls. The 255-byte local FIFO payload buffers remain on the stack in both paths. - Sharing is safe: the window is never active in both directions at the
same time (both paths run under
&mut self), and thetoolscodec fully re-initializes the window on every call.
With a statically placed Controller (RTIC resource / static) total peak
RAM is roughly unchanged; a stack-allocated Controller grows its
construction frame by 2048 bytes but loses the matching 2048-byte window
spike inside send.
Blocking delays
The driver is blocking: construction, send, and receive busy-wait fixed
intervals. Every interval is a named constant in src/sx1278/params.rs, and
every one of them is empirical; origin unclear — measure before changing:
| Constant | Value | Purpose |
|---|---|---|
RESET_PULSE_MS |
100 ms | RST held low during the hardware reset in Controller::new |
POST_RESET_SETTLE_MS |
100 ms | Settle after releasing RST, before the first register write |
SLEEP_ENTRY_SETTLE_MS |
100 ms | Settle after switching the chip to LoRa sleep mode, before configuration |
BOOT_VERSION_DELAY_MS |
1000 ms | Unconditional boot wait before the first version read in Controller::new |
TX_START_DELAY_MS |
1 ms | Settle before switching the radio to TX mode in send |
POST_TX_GUARD_MS |
10 ms | Guard after TX completes and the mode is restored in send |
RX_ENTRY_SETTLE_MS |
50 ms | Settle after entering RX, before the IRQ flags are sampled in receive |
ABORT_SETTLE_MS |
5 ms | Settle after clearing IRQ flags and FIFO pointers in the abort path, before the mode is restored |
OSC_STARTUP_MS |
5 ms | Wait after leaving deep sleep for the oscillator to restart, before the mode is restored |
Controller::new runs the first four delays, so initialization takes about
1.3 s in total. Every send ends with the 10 ms post-TX guard, and every
receive call waits the RX poll interval before sampling the IRQ flags. That
interval is runtime-configurable via Controller::set_rx_poll_interval_ms(ms)
(getter: rx_poll_interval_ms()): it defaults to 50 ms (~20 polls/s) and 0
removes the pre-poll wait entirely, for IRQ-driven callers that only poll
after a DIO0 interrupt.
Note that handle_dio0_irq wakes the chip automatically: when invoked while
the chip is in deep sleep it runs the full wake sequence (a register write,
the 5 ms oscillator wait, and a mode restore), not just the flag read.
To measure a delay on hardware, toggle a GPIO high immediately before the
delay_ms call and low immediately after, then measure the high pulse with
an oscilloscope or logic analyzer. Repeat for every constant you intend to
change, and adjust the value only after measuring: the current figures were
raised for stability and their origin is undocumented.
Security characteristics
The driver sends and receives payloads without message authentication or
confidentiality: protection is limited to the LoRa PHY and the chip's
payload CRC, which is an integrity check, not a cryptographic measure. The
sync word (chip default 0x12) distinguishes traffic but is not an access
control. Any application-level authentication or encryption layer is the
application's responsibility.
Regulatory compliance is likewise the application's responsibility: the
driver rejects frequencies outside the SX1278 LF band (137-525 MHz), but duty
cycle and TX power are not enforced. The power defaults are conservative:
PA_BOOST with output level 3, the normal PA_DAC setting, and
over-current protection enabled.
Received-payload integrity is always on: the payload CRC is forced enabled by
the driver and cannot be disabled through the public API; a packet that fails
it raises LoRaError::CrcError instead of being delivered.
Project structure
lora/
├── src/
│ ├── lib.rs # Crate root; re-exports error and sx1278
│ ├── error/ # LoRaError and error types
│ │ ├── mod.rs
│ │ ├── model.rs
│ │ └── tests/
│ │ └── mod.rs
│ └── sx1278/ # SX1278 driver (feature-gated)
│ ├── mod.rs # Controller, Mode, ReceiveResult, TxTimeoutConfig
│ ├── params.rs # Named timing constants and FIFO/payload limits
│ ├── registers.rs # Register addresses, modes, IRQ masks, constants
│ └── tests/ # Unit tests (send, receive, set_frequency, set_mode, version, etc.)
├── scripts/
│ └── test.sh # Runs cargo test with sx1278 feature
├── Cargo.toml
├── LICENSE
└── README.md
Dependencies
- embedded-hal 1.x — SPI, delay, and output pin traits.
- log (default-features = false) —
no_std-compatible logging. - tools (Forgejo registry) — Register bus access (
registerfeature) and payload compression (compressionfeature); requires a configured Forgejo Cargo registry.
Building and testing
# Build with SX1278 support
cargo build --features sx1278
# Run tests (uses embedded-hal-mock)
cargo test --features sx1278
# Or use the project script
./scripts/test.sh
Status
- Version: 0.4.0
- Driver: SX1278 register interface for the 137-525 MHz band, blocking API: init, send, receive, set_frequency, set_mode, deep sleep, read_version.
- Tests: 47 unit tests covering init, send (including timeout and cancellation), receive (including CRC, buffer size, and RSSI/SNR values), set_frequency, set_mode, deep sleep, and version read.
License
Personal Use Only — Non-Commercial. See LICENSE for full terms and disclaimers. Commercial use requires written permission from the copyright holder.
Dependencies
| ID | Version |
|---|---|
| embedded-hal | ^1.0.0 |
| log | ^0.4 |
| tools | ^1.2.1 |
| embedded-hal-mock | ^0.11.1 |