8.5 KiB
Micro controller LoRa KISS modem
This project is a LoRa KISS modem for common LoRa capable development boards, such as created by vendors like Heltec, RAK, Seeed, etc.
It is using the USB-serial interface (optionally hardware serial interface) to allow the user to send and receive LoRa packets using the KISS modem protocol.
KISS Modem
KISS Framing
KISS (Keep It Simple, Stupid) is a standard TNC protocol for packet radio. Frames use special bytes for delimiting:
| Symbol | Value | Meaning |
|---|---|---|
| FEND | 0xC0 |
Frame end/begin delimiter |
| FESC | 0xDB |
Frame escape |
| TFEND | 0xDC |
Transposed FEND (follows FESC) |
| TFESC | 0xDD |
Transposed FESC (follows FESC) |
A frame has the structure:
FEND <type byte> <data bytes...> FEND
The type byte encodes the port number (upper nibble) and command (lower nibble):
type = (port << 4) | cmd
For host-to-modem data frames, cmd = 0x00. Any FEND or FESC bytes
appearing in the data payload must be escaped as FESC TFEND or FESC TFESC
respectively.
Ports
The KISS modem uses three ports for communication:
| Port | Direction | Purpose |
|---|---|---|
| 0 | bidirectional | Raw LoRa packet data |
| 1 | TNC → host | Signal quality (SNR + RSSI) |
| 2 | bidirectional | Radio configuration commands |
Port 1 — Signal Quality
Before each received LoRa packet delivered on port 0, the modem emits a signal quality frame on port 1. The payload is 3 bytes, big-endian:
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 1 | int8 | SNR | Signal-to-noise ratio in dB (rounded) |
| 1 | 2 | int16 | RSSI | Received signal strength in dBm |
Example: FEND 0x11 [-7] [0xFF 0x99] FEND = SNR = -7 dB, RSSI = -103 dBm
Configuration Commands (Port 2)
Configuration commands allow the host to query and control the modem's LoRa
parameters. Each configuration frame's data payload (after the KISS type byte)
begins with a 1-byte command byte. The modem responds to every SET_* command
with either RES_OK (0x01) or RES_ERROR (0x02) on port 2.
Commands:
| Cmd | Name | Dir | Payload |
|---|---|---|---|
| 0x00 | Reserved | — | — |
| 0x01 | RES_OK | ← | (none) |
| 0x02 | RES_ERROR | ← | (none) |
| 0x10 | GET_RADIO | → | (none) |
| 0x10 | resp | ← | freq_kHz, bw_hz, sf, cr, power |
| 0x11 | SET_RADIO | → | freq_kHz, bw_hz, sf, cr, power |
| 0x12 | GET_FREQUENCY | → | (none) |
| 0x12 | resp | ← | freq_kHz |
| 0x13 | SET_FREQUENCY | → | freq_kHz |
| 0x14 | GET_BANDWIDTH | → | (none) |
| 0x14 | resp | ← | bw_hz |
| 0x15 | SET_BANDWIDTH | → | bw_hz |
| 0x16 | GET_SF | → | (none) |
| 0x16 | resp | ← | sf |
| 0x17 | SET_SF | → | sf |
| 0x18 | GET_CR | → | (none) |
| 0x18 | resp | ← | cr |
| 0x19 | SET_CR | → | cr |
| 0x1A | GET_POWER | → | (none) |
| 0x1A | resp | ← | power_dBm |
| 0x1B | SET_POWER | → | power_dBm |
| 0x1C | GET_SYNCWORD | → | (none) |
| 0x1C | resp | ← | syncword |
| 0x1D | SET_SYNCWORD | → | syncword |
Legend: → = host → TNC, ← = TNC → host
Units
All integer fields are transmitted in big-endian byte order.
| Parameter | Type | Unit | Encoding | Example |
|---|---|---|---|---|
| Frequency | uint32 | kHz | 4 bytes | 869.618 MHz → 869618 |
| Bandwidth | uint32 | Hz | 4 bytes | 62.5 kHz → 62500 |
| SF | uint8 | — | 1 byte (5–12) | SF 7 → 0x07 |
| CR | uint8 | — | 1 byte (5–8) | 4/5 → 0x05 |
| Power | int8 | dBm | 1 byte | 22 dBm → 0x16 |
| Sync word | uint8 | — | 1 byte | 0x34 → public |
LoRa
Hardware
Common LoRa hardware is supported via the RadioLib library:
| Module | Frequency band | Notes |
|---|---|---|
| SX1262 | 868 / 915 MHz | Used on Heltec, RAK, Seeed boards |
| SX1268 | 433 / 470 MHz | 433 MHz ISM band variants |
| LR1110 | 150–960 MHz | Semtech LR11xx family |
| LR1120 | 150–960 MHz, 2.4 GHz | Semtech LR11xx family |
| SX1276 / SX1278 | 137–1020 MHz | Older boards; supported via RadioLib |
Bandwidths
All LoRa bandwidths supported by RadioLib are accepted. SX126x-based boards support:
| Bandwidth | Wire value (uint32 Hz) |
|---|---|
| 7.8 kHz | 7800 |
| 10.4 kHz | 10400 |
| 15.6 kHz | 15600 |
| 20.8 kHz | 20800 |
| 31.25 kHz | 31250 |
| 41.7 kHz | 41700 |
| 62.5 kHz | 62500 |
| 125 kHz | 125000 |
| 250 kHz | 250000 |
| 500 kHz | 500000 |
Note: SX1272/SX1273-based boards support only 125, 250, and 500 kHz. Not all bandwidths are legal for all frequency bands — consult local regulations.
Project Structure
loramodem/
├── platformio.ini # Board environments and build configuration
├── PROJECT.md # This file
├── src/
│ ├── main.cpp # Entry point; Arduino setup() and loop()
│ ├── kiss.h / kiss.cpp # KISS frame encoder/decoder
│ ├── radio.h / radio.cpp # RadioLib wrapper (init, TX, RX, config)
│ └── config.h # Default radio parameters and pin definitions
├── include/ # Shared headers (if needed)
├── lib/ # Local libraries (if needed)
├── variants/ # Board-specific hardware definitions
│ ├── heltec_wifi_lora_32_v3/pins.h
│ ├── rak4631/pins.h
│ └── [other boards]/pins.h
└── test/ # PlatformIO Unity test suite
Building
Prerequisites: PlatformIO CLI or IDE extension.
Build for a specific board:
pio run -e heltec_wifi_lora_32_v3
Upload to a connected board:
pio run -e heltec_wifi_lora_32_v3 -t upload
Monitor serial output:
pio device monitor -b 115200
Note: KISS frames are binary. Use a KISS-capable TNC client (e.g., Direwolf, socat) to interact with the modem over serial.
Available board environments are defined in platformio.ini.
Configuration Defaults
The modem initializes with these LoRa parameters at power-on:
| Parameter | Default value |
|---|---|
| Frequency | TBD MHz |
| Bandwidth | 125 kHz |
| SF | TBD |
| CR | 4/5 |
| Power | TBD dBm |
| Sync word | 0x34 (LoRa public) |
These defaults may be overridden at compile time via config.h. Parameters changed
via KISS port 2 commands take effect immediately but are not persisted across
power cycles unless persistent storage (EEPROM/NVS) is implemented.
Framework
This project uses PlatformIO as the build system with
RadioLib as the LoRa driver library.
Board-specific pin assignments and hardware configurations are isolated in the
variants/ directory, allowing the core KISS modem logic to remain board-agnostic.
The USB-CDC serial interface is used by default for KISS communication. On supported
boards, a hardware UART may be selected instead via compile-time configuration in config.h.