ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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
15
16#if PC_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 pc_lora_bus *b, uint8_t reg)
67{
68 return b->read(reg, b->ctx);
69}
70inline void wr(const pc_lora_bus *b, uint8_t reg, uint8_t val)
71{
72 b->write(reg, val, b->ctx);
73}
74} // namespace
75
76bool pc_lora_frame_parse(const uint8_t *raw, uint16_t len, pc_lora_header *hdr, const uint8_t **payload,
77 uint16_t *payload_len)
78{
79 if (!raw || !hdr || len < 4)
80 {
81 return false;
82 }
83 hdr->to = raw[0];
84 hdr->from = raw[1];
85 hdr->id = raw[2];
86 hdr->flags = raw[3];
87 if (payload)
88 {
89 *payload = raw + 4;
90 }
91 if (payload_len)
92 {
93 *payload_len = (uint16_t)(len - 4);
94 }
95 return true;
96}
97
98uint16_t pc_lora_frame_build(const pc_lora_header *hdr, const uint8_t *payload, uint16_t len, uint8_t *out,
99 uint16_t cap)
100{
101 if (!hdr || !out || len > PC_LORA_MAX_PAYLOAD || (uint32_t)len + 4 > cap)
102 {
103 return 0;
104 }
105 out[0] = hdr->to;
106 out[1] = hdr->from;
107 out[2] = hdr->id;
108 out[3] = hdr->flags;
109 for (uint16_t i = 0; i < len; i++)
110 {
111 out[4 + i] = payload[i];
112 }
113 return (uint16_t)(len + 4);
114}
115
116bool pc_lora_init(const pc_lora_bus *bus, const pc_lora_config *cfg)
117{
118 if (!bus || !bus->read || !bus->write || !cfg)
119 {
120 return false;
121 }
122 if (rd(bus, LoraReg::REG_VERSION) != SX127X_VERSION)
123 {
124 return false; // the bus is not talking to an SX127x
125 }
126
127 // Switch to LoRa mode (only settable from sleep), then standby.
128 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_SLEEP);
129 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_SLEEP);
130 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_STDBY);
131
132 // Carrier frequency: Frf = freq / FSTEP, FSTEP = 32 MHz / 2^19.
133 uint32_t frf = (uint32_t)(((uint64_t)cfg->freq_hz << 19) / 32000000UL);
134 wr(bus, LoraReg::REG_FRF_MSB, (uint8_t)(frf >> 16));
135 wr(bus, LoraReg::REG_FRF_MID, (uint8_t)(frf >> 8));
136 wr(bus, LoraReg::REG_FRF_LSB, (uint8_t)frf);
137
138 wr(bus, LoraReg::REG_FIFO_TX_BASE, 0x00);
139 wr(bus, LoraReg::REG_FIFO_RX_BASE, 0x00);
140
141 // Modem config: explicit header, CRC on, AGC auto; low-data-rate optimize at SF11/12.
142 wr(bus, LoraReg::REG_MODEM_CONFIG1, (uint8_t)((cfg->bandwidth << 4) | (cfg->coding_rate << 1)));
143 wr(bus, LoraReg::REG_MODEM_CONFIG2, (uint8_t)((cfg->spreading << 4) | 0x04));
144 wr(bus, LoraReg::REG_MODEM_CONFIG3, (uint8_t)((cfg->spreading >= 11 ? 0x08 : 0x00) | 0x04));
145
146 wr(bus, LoraReg::REG_PREAMBLE_MSB, 0x00);
147 wr(bus, LoraReg::REG_PREAMBLE_LSB, 0x08);
148 wr(bus, LoraReg::REG_SYNC_WORD, cfg->sync_word);
149 wr(bus, LoraReg::REG_PA_CONFIG, (uint8_t)(0x80 | ((cfg->tx_power - 2) & 0x0F))); // PA_BOOST pin
150
151 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_STDBY);
152 return true;
153}
154
155bool pc_lora_send(const pc_lora_bus *bus, const uint8_t *frame, uint8_t len)
156{
157 if (!bus || !frame || len == 0 || len > PC_LORA_MAX_PAYLOAD + 4)
158 {
159 return false;
160 }
161 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_STDBY);
162 wr(bus, LoraReg::REG_FIFO_ADDR_PTR, 0x00);
163 for (uint8_t i = 0; i < len; i++)
164 {
165 wr(bus, LoraReg::REG_FIFO, frame[i]);
166 }
167 wr(bus, LoraReg::REG_PAYLOAD_LENGTH, len);
168 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_TX);
169 return true;
170}
171
172bool pc_lora_tx_done(const pc_lora_bus *bus)
173{
174 if (!bus)
175 {
176 return false;
177 }
178 if (rd(bus, LoraReg::REG_IRQ_FLAGS) & LoraIrq::IRQ_TX_DONE)
179 {
180 wr(bus, LoraReg::REG_IRQ_FLAGS, 0xFF); // clear all IRQ flags
181 return true;
182 }
183 return false;
184}
185
186void pc_lora_set_rx(const pc_lora_bus *bus)
187{
188 if (!bus)
189 {
190 return;
191 }
192 wr(bus, LoraReg::REG_FIFO_ADDR_PTR, 0x00);
193 wr(bus, LoraReg::REG_OP_MODE, LoraMode::MODE_LORA | LoraMode::MODE_RX_CONT);
194}
195
196int pc_lora_recv(const pc_lora_bus *bus, uint8_t *buf, uint8_t cap, int16_t *rssi)
197{
198 if (!bus || !buf)
199 {
200 return -1;
201 }
202 uint8_t flags = rd(bus, LoraReg::REG_IRQ_FLAGS);
203 if (!(flags & LoraIrq::IRQ_RX_DONE))
204 {
205 return -1; // nothing received
206 }
207 if (flags & LoraIrq::IRQ_PAYLOAD_CRC_ERROR)
208 {
209 wr(bus, LoraReg::REG_IRQ_FLAGS, 0xFF);
210 return -1; // corrupt frame, dropped
211 }
212 uint8_t len = rd(bus, LoraReg::REG_RX_NB_BYTES);
213 wr(bus, LoraReg::REG_FIFO_ADDR_PTR, rd(bus, LoraReg::REG_FIFO_RX_CURRENT));
214 uint8_t n = 0;
215 for (uint8_t i = 0; i < len; i++)
216 {
217 uint8_t b = rd(bus, LoraReg::REG_FIFO); // advances the FIFO pointer
218 if (n < cap)
219 {
220 buf[n++] = b;
221 }
222 }
223 if (rssi)
224 {
225 *rssi = (int16_t)(-157 + rd(bus, LoraReg::REG_PKT_RSSI)); // HF port (868/915 MHz)
226 }
227 wr(bus, LoraReg::REG_IRQ_FLAGS, 0xFF);
228 return (int)n;
229}
230
231#endif // PC_ENABLE_LORA
LoRa radio codec + driver (PC_ENABLE_LORA) - Semtech SX127x / RFM95-96.
#define PC_LORA_MAX_PAYLOAD
Max LoRa payload bytes (SX127x FIFO is 256; RadioHead uses 251 + 4 header).