DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
lora.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file lora.cpp
6 * @brief LoRa codec + SX127x driver - implementation.
7 *
8 * The codec is the RadioHead 4-byte header. The driver speaks the SX1276/77/78/79 LoRa
9 * register protocol (datasheet register map below) through the caller's register-access
10 * bus, so the sequence is host-testable with a mock register file and portable across SPI
11 * peripherals. The RF link itself needs the module.
12 */
13
14#include "services/lora/lora.h"
15
16#if DETWS_ENABLE_LORA
17
18namespace
19{
20// SX127x LoRa register map (SX1276 datasheet, Table 41).
21struct LoraReg
22{
23 static constexpr uint8_t REG_FIFO = 0x00;
24 static constexpr uint8_t REG_OP_MODE = 0x01;
25 static constexpr uint8_t REG_FRF_MSB = 0x06;
26 static constexpr uint8_t REG_FRF_MID = 0x07;
27 static constexpr uint8_t REG_FRF_LSB = 0x08;
28 static constexpr uint8_t REG_PA_CONFIG = 0x09;
29 static constexpr uint8_t REG_FIFO_ADDR_PTR = 0x0D;
30 static constexpr uint8_t REG_FIFO_TX_BASE = 0x0E;
31 static constexpr uint8_t REG_FIFO_RX_BASE = 0x0F;
32 static constexpr uint8_t REG_FIFO_RX_CURRENT = 0x10;
33 static constexpr uint8_t REG_IRQ_FLAGS = 0x12;
34 static constexpr uint8_t REG_RX_NB_BYTES = 0x13;
35 static constexpr uint8_t REG_PKT_RSSI = 0x1A;
36 static constexpr uint8_t REG_MODEM_CONFIG1 = 0x1D;
37 static constexpr uint8_t REG_MODEM_CONFIG2 = 0x1E;
38 static constexpr uint8_t REG_PREAMBLE_MSB = 0x20;
39 static constexpr uint8_t REG_PREAMBLE_LSB = 0x21;
40 static constexpr uint8_t REG_PAYLOAD_LENGTH = 0x22;
41 static constexpr uint8_t REG_MODEM_CONFIG3 = 0x26;
42 static constexpr uint8_t REG_SYNC_WORD = 0x39;
43 static constexpr uint8_t REG_VERSION = 0x42;
44};
45
46// RegOpMode: LongRangeMode bit + transceiver mode.
47struct LoraMode
48{
49 static constexpr uint8_t MODE_LORA = 0x80;
50 static constexpr uint8_t MODE_SLEEP = 0x00;
51 static constexpr uint8_t MODE_STDBY = 0x01;
52 static constexpr uint8_t MODE_TX = 0x03;
53 static constexpr uint8_t MODE_RX_CONT = 0x05;
54};
55
56// RegIrqFlags.
57struct LoraIrq
58{
59 static constexpr uint8_t IRQ_TX_DONE = 0x08;
60 static constexpr uint8_t IRQ_PAYLOAD_CRC_ERROR = 0x20;
61 static constexpr uint8_t IRQ_RX_DONE = 0x40;
62};
63
64const uint8_t SX127X_VERSION = 0x12;
65
66inline uint8_t rd(const lora_bus *b, uint8_t reg)
67{
68 return b->read(reg, b->ctx);
69}
70inline void wr(const lora_bus *b, uint8_t reg, uint8_t val)
71{
72 b->write(reg, val, b->ctx);
73}
74} // namespace
75
76bool lora_frame_parse(const uint8_t *raw, uint16_t len, lora_header *hdr, const uint8_t **payload,
77 uint16_t *payload_len)
78{
79 if (!raw || !hdr || len < 4)
80 return false;
81 hdr->to = raw[0];
82 hdr->from = raw[1];
83 hdr->id = raw[2];
84 hdr->flags = raw[3];
85 if (payload)
86 *payload = raw + 4;
87 if (payload_len)
88 *payload_len = (uint16_t)(len - 4);
89 return true;
90}
91
92uint16_t lora_frame_build(const lora_header *hdr, const uint8_t *payload, uint16_t len, uint8_t *out, uint16_t cap)
93{
94 if (!hdr || !out || len > DETWS_LORA_MAX_PAYLOAD || (uint32_t)len + 4 > cap)
95 return 0;
96 out[0] = hdr->to;
97 out[1] = hdr->from;
98 out[2] = hdr->id;
99 out[3] = hdr->flags;
100 for (uint16_t i = 0; i < len; i++)
101 out[4 + i] = payload[i];
102 return (uint16_t)(len + 4);
103}
104
105bool lora_init(const lora_bus *bus, const lora_config *cfg)
106{
107 if (!bus || !bus->read || !bus->write || !cfg)
108 return false;
109 if (rd(bus, LoraReg::REG_VERSION) != SX127X_VERSION)
110 return false; // the bus is not talking to an SX127x
111
112 // Switch to LoRa mode (only settable from sleep), then standby.
113 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_SLEEP);
114 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_SLEEP);
115 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_STDBY);
116
117 // Carrier frequency: Frf = freq / FSTEP, FSTEP = 32 MHz / 2^19.
118 uint32_t frf = (uint32_t)(((uint64_t)cfg->freq_hz << 19) / 32000000UL);
119 wr(bus, LoraReg::REG_FRF_MSB, (uint8_t)(frf >> 16));
120 wr(bus, LoraReg::REG_FRF_MID, (uint8_t)(frf >> 8));
121 wr(bus, LoraReg::REG_FRF_LSB, (uint8_t)frf);
122
123 wr(bus, LoraReg::REG_FIFO_TX_BASE, 0x00);
124 wr(bus, LoraReg::REG_FIFO_RX_BASE, 0x00);
125
126 // Modem config: explicit header, CRC on, AGC auto; low-data-rate optimize at SF11/12.
127 wr(bus, LoraReg::REG_MODEM_CONFIG1, (uint8_t)((cfg->bandwidth << 4) | (cfg->coding_rate << 1)));
128 wr(bus, LoraReg::REG_MODEM_CONFIG2, (uint8_t)((cfg->spreading << 4) | 0x04));
129 wr(bus, LoraReg::REG_MODEM_CONFIG3, (uint8_t)((cfg->spreading >= 11 ? 0x08 : 0x00) | 0x04));
130
131 wr(bus, LoraReg::REG_PREAMBLE_MSB, 0x00);
132 wr(bus, LoraReg::REG_PREAMBLE_LSB, 0x08);
133 wr(bus, LoraReg::REG_SYNC_WORD, cfg->sync_word);
134 wr(bus, LoraReg::REG_PA_CONFIG, (uint8_t)(0x80 | ((cfg->tx_power - 2) & 0x0F))); // PA_BOOST pin
135
136 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_STDBY);
137 return true;
138}
139
140bool lora_send(const lora_bus *bus, const uint8_t *frame, uint8_t len)
141{
142 if (!bus || !frame || len == 0 || len > DETWS_LORA_MAX_PAYLOAD + 4)
143 return false;
144 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_STDBY);
145 wr(bus, LoraReg::REG_FIFO_ADDR_PTR, 0x00);
146 for (uint8_t i = 0; i < len; i++)
147 wr(bus, LoraReg::REG_FIFO, frame[i]);
148 wr(bus, LoraReg::REG_PAYLOAD_LENGTH, len);
149 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_TX);
150 return true;
151}
152
153bool lora_tx_done(const lora_bus *bus)
154{
155 if (!bus)
156 return false;
157 if (rd(bus, LoraReg::REG_IRQ_FLAGS) & LoraIrq::IRQ_TX_DONE)
158 {
159 wr(bus, LoraReg::REG_IRQ_FLAGS, 0xFF); // clear all IRQ flags
160 return true;
161 }
162 return false;
163}
164
165void lora_set_rx(const lora_bus *bus)
166{
167 if (!bus)
168 return;
169 wr(bus, LoraReg::REG_FIFO_ADDR_PTR, 0x00);
170 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_RX_CONT);
171}
172
173int lora_recv(const lora_bus *bus, uint8_t *buf, uint8_t cap, int16_t *rssi)
174{
175 if (!bus || !buf)
176 return -1;
177 uint8_t flags = rd(bus, LoraReg::REG_IRQ_FLAGS);
178 if (!(flags & LoraIrq::IRQ_RX_DONE))
179 return -1; // nothing received
180 if (flags & LoraIrq::IRQ_PAYLOAD_CRC_ERROR)
181 {
182 wr(bus, LoraReg::REG_IRQ_FLAGS, 0xFF);
183 return -1; // corrupt frame, dropped
184 }
185 uint8_t len = rd(bus, LoraReg::REG_RX_NB_BYTES);
186 wr(bus, LoraReg::REG_FIFO_ADDR_PTR, rd(bus, LoraReg::REG_FIFO_RX_CURRENT));
187 uint8_t n = 0;
188 for (uint8_t i = 0; i < len; i++)
189 {
190 uint8_t b = rd(bus, LoraReg::REG_FIFO); // advances the FIFO pointer
191 if (n < cap)
192 buf[n++] = b;
193 }
194 if (rssi)
195 *rssi = (int16_t)(-157 + rd(bus, LoraReg::REG_PKT_RSSI)); // HF port (868/915 MHz)
196 wr(bus, LoraReg::REG_IRQ_FLAGS, 0xFF);
197 return (int)n;
198}
199
200#endif // DETWS_ENABLE_LORA
#define DETWS_LORA_MAX_PAYLOAD
Max LoRa payload bytes (SX127x FIFO is 256; RadioHead uses 251 + 4 header).
LoRa radio codec + driver (DETWS_ENABLE_LORA) - Semtech SX127x / RFM95-96.