ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
lora.h
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.h
6 * @brief LoRa radio codec + driver (PC_ENABLE_LORA) - Semtech SX127x / RFM95-96.
7 *
8 * A per-radio plugin for the gateway (PC_ENABLE_GATEWAY): the southbound-radio half of
9 * a LoRa-to-web bridge. Two layers:
10 *
11 * - **Codec** - the RadioHead-compatible 4-byte frame header (`to` / `from` / `id` /
12 * `flags`) that virtually every hobby / sensor LoRa deployment uses on top of the
13 * header-less LoRa PHY. pc_lora_frame_parse() splits a received frame into that header and
14 * the payload; pc_lora_frame_build() prepends it. Pure, no hardware.
15 * - **Driver** - the SX127x register protocol (init / send / receive / enter-RX) over a
16 * caller-supplied register-access **bus** (@ref pc_lora_bus). The SPI transfer and the
17 * chip-select / reset GPIOs are the integration's - you implement two callbacks that
18 * read and write a chip register - so the register sequence is host-testable with a mock
19 * bus and portable across whatever SPI peripheral you wire the module to.
20 *
21 * Wiring to the gateway (see example LoRaGateway): poll pc_lora_recv(); on a frame,
22 * pc_lora_frame_parse() then pc_gateway_uplink(port, header.from, payload, len, rssi). A downlink
23 * builds a frame with pc_lora_frame_build() and pc_lora_send()s it. The codec + register protocol
24 * are verified on the host; the RF link itself needs the module.
25 *
26 * @author Douglas Quigg (dstroy0)
27 * @date 2026
28 */
29
30#ifndef PROTOCORE_LORA_H
31#define PROTOCORE_LORA_H
32
33#include "protocore_config.h"
34
35#if PC_ENABLE_LORA
36
37#include <stddef.h>
38#include <stdint.h>
39
40// --- Codec: the RadioHead RH_RF95 4-byte header ---------------------------------------
41
42/** @brief RadioHead-compatible LoRa frame header (precedes the payload). */
43struct pc_lora_header
44{
45 uint8_t to; ///< destination node address (0xFF = broadcast)
46 uint8_t from; ///< source node address
47 uint8_t id; ///< sequence / message id
48 uint8_t flags; ///< application flags
49};
50
51/**
52 * @brief Split a received frame into its header and payload.
53 * @param[out] payload set to the first payload byte (points into @p raw).
54 * @param[out] payload_len set to the payload length.
55 * @return true; false if @p raw is shorter than the 4-byte header.
56 */
57bool pc_lora_frame_parse(const uint8_t *raw, uint16_t len, pc_lora_header *hdr, const uint8_t **payload,
58 uint16_t *payload_len);
59
60/**
61 * @brief Build a frame (header + payload) into @p out.
62 * @return the total frame length, or 0 if it would not fit @p cap or exceeds the payload max.
63 */
64uint16_t pc_lora_frame_build(const pc_lora_header *hdr, const uint8_t *payload, uint16_t len, uint8_t *out,
65 uint16_t cap);
66
67// --- Driver: SX127x over a register-access bus ----------------------------------------
68
69/** @brief Read one SX127x register (@p reg is the bare 7-bit address). */
70using pc_lora_reg_read_fn = uint8_t (*)(uint8_t reg, void *ctx);
71/** @brief Write one SX127x register (@p reg is the bare 7-bit address). */
72using pc_lora_reg_write_fn = void (*)(uint8_t reg, uint8_t val, void *ctx);
73
74/** @brief The register-access bus a driver call uses (your SPI + chip-select behind it). */
75struct pc_lora_bus
76{
77 pc_lora_reg_read_fn read;
78 pc_lora_reg_write_fn write;
79 void *ctx;
80};
81
82/** @brief Radio configuration applied by pc_lora_init(). */
83struct pc_lora_config
84{
85 uint32_t freq_hz; ///< carrier frequency in Hz (e.g. 868100000 / 915000000).
86 uint8_t spreading; ///< spreading factor 6..12 (SF7 default is a good start).
87 uint8_t bandwidth; ///< bandwidth code 0..9 (7 = 125 kHz - the common default).
88 uint8_t coding_rate; ///< coding rate 1..4 (1 = 4/5).
89 uint8_t sync_word; ///< 0x12 private / 0x34 LoRaWAN.
90 uint8_t tx_power; ///< PA_BOOST power 2..17 dBm.
91};
92
93/**
94 * @brief Initialize the SX127x: verify the chip, switch to LoRa mode, and apply @p cfg.
95 * @return true; false if the register at RegVersion is not the SX127x id (0x12) - i.e. the
96 * bus is not talking to the chip.
97 */
98bool pc_lora_init(const pc_lora_bus *bus, const pc_lora_config *cfg);
99
100/**
101 * @brief Load @p frame into the FIFO and start a transmit (the radio returns to standby on
102 * TxDone). Poll pc_lora_tx_done() for completion.
103 * @return true; false if @p len exceeds PC_LORA_MAX_PAYLOAD + 4.
104 */
105bool pc_lora_send(const pc_lora_bus *bus, const uint8_t *frame, uint8_t len);
106
107/** @brief True once a transmit has finished (RegIrqFlags TxDone); clears the flag. */
108bool pc_lora_tx_done(const pc_lora_bus *bus);
109
110/** @brief Put the radio in continuous-receive mode (call once, then poll pc_lora_recv()). */
111void pc_lora_set_rx(const pc_lora_bus *bus);
112
113/**
114 * @brief If a frame has been received, copy it into @p buf and report its RSSI.
115 * @param[out] rssi set to the packet RSSI in dBm (may be null).
116 * @return the frame length (>=0), or -1 if no frame is ready or the CRC failed.
117 */
118int pc_lora_recv(const pc_lora_bus *bus, uint8_t *buf, uint8_t cap, int16_t *rssi);
119
120#endif // PC_ENABLE_LORA
121
122#endif // PROTOCORE_LORA_H
User-facing configuration for ProtoCore.