Skip to content

Commit 01a880f

Browse files
authored
Merge pull request #999 from TristanWebber/sx126x_examples
add sx1262 example, update readme
2 parents de27e27 + 6381ce8 commit 01a880f

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,9 @@ This library provides several examples.
551551
transmits temperature and relative humidity using a DHT22 sensor. It's only
552552
been tested with Feather M0-family products.
553553

554+
- [`ttn-otaa-sx1262.ino`](examples/ttn-otaa-sx1262/ttn-otaa-sx1262.ino) is a version of `ttn-otaa.ino` that demonstrates [Advanced initialization](doc/HOWTO-Manually-Configure.md#advanced-initialization) techniques
555+
that will be required for some boards, in particular, any SX126x transceiver that is not pre-integrated.
556+
554557
- [`raw.ino`](examples/raw/raw.ino) shows how to access the radio on a somewhat low level,
555558
and allows to send raw (non-LoRaWAN) packets between nodes directly.
556559
This is useful to verify basic connectivity, and when no gateway is

doc/HOWTO-Manually-Configure.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ pins for different parts (like the Semtech evaluation board that has
3434
`VDD_RF`, `VDD_ANA` and `VDD_FEM`), which can all be connected together.
3535
Any *GND* pins need to be connected to the Arduino *GND* pin(s).
3636

37+
The SX126x transceivers are able to be physically configured to use a DC-DC buck converter or linear regulator LDO type of voltage regulation. The DC-DC regulator is enabled by wiring an inductor between the `VREG` and `DCC_SW` pins, and if this is the case, it will generally be described in the technical literature for the board. The library selects LDO by default, however DC-DC can be configured as described in [Advanced initialization](HOWTO-Manually-Configure.md#advanced-initialization).
38+
3739
### SPI
3840

3941
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
@@ -91,6 +93,8 @@ connected.
9193
The pins used on the Arduino side should be configured in the pin
9294
mapping in your sketch, by setting the values of `lmic_pinmap::dio[0]`, `[1]`, and `[2]` (see [below](#pin-mapping)).
9395

96+
The SX126x transceivers differ substantially from the SX127x transceivers in their I/O principle of operation. The modem uses a BUSY control line to indicate whether the transceiver is ready to accept a command from the host controller. The three DIOs (DIO1, DIO2 and DIO3) are all able to be configured as IRQs, however DIO2 and DIO3 can also be used to control an external [RfSwitch](HOWTO-Manually-Configure.md#rxtx) and TCXO respectively. For this reason, the driver used in this library exclusively uses DIO1 for interrupts. In the event a user of the library identifies a board that is not compatible with this arrangement, please create an issue.
97+
9498
## Reset
9599

96100
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
@@ -227,6 +231,12 @@ public:
227231
};
228232
```
229233

234+
> [!NOTE]
235+
> Any manually configured board using SX126x transceivers will need
236+
> to use advanced configuration. At a minimum, `queryBusyPin(void)`
237+
> will need to be overridden to ensure the host is able to communicate
238+
> with the radio.
239+
230240
## HalConfiguration_t methods
231241

232242
- `ostime_t setModuleActive(bool state)` is called by the LMIC to make the module active or to deactivate it (the value of `state` is true to activate). The implementation must turn power to the module on and otherwise prepare for it to go to work, and must return the number of OS ticks to wait before starting to use the radio.
@@ -239,6 +249,14 @@ public:
239249

240250
- `TxPowerPolicy_t getTxPowerPolicy(TxPowerPolicy_t policy, int8_t requestedPower, uint32_t frequency)` allows you to override the LMIC's selection of transmit power. If not provided, the default method forces the LMIC to use PA_BOOST mode. (We chose to do this because we found empirically that the Hope RF module doesn't support RFO, and because legacy LMIC code never used anything except PA_BOOST mode.)
241251

252+
- `queryBusyPin(void)` shall return the host controller pin that is wired to the busy pin of a SX126x family transceiver. This is mandatory for all SX126x transceivers because their communications with the host controller is different than the SX127x family.
253+
254+
- `queryUsingDcdc(void)` shall return true when a SX126x transceiver is wired to use the DC-DC buck converter for voltage regulation. The default (linear regulator LDO) will work in all cases, however where the DC-DC regulator is physically and used in software, improved power consumption is achieved.
255+
256+
- `queryUsingDIO2AsRfSwitch(void)` shall return true when a transceiver is physically configured to switch an external antenna with DIO2. When this configuration is physically enabled and selected in software, the host will allow the radio to control the RfSwitch, generally resulting in better control timing.
257+
258+
- `queryUsingDIO3AsTCXOSwitch(void)` shall return true when a SX126x transceiver is physically configured with DIO3 driving an external temperature controlled crystal oscillator (TXCO).
259+
242260
Caution: the LMIC has no way of knowing whether the mode you return makes sense. Use of 20 dBm mode without limiting duty cycle can over-stress your module. The LMIC currently does not have any code to duty-cycle US transmissions at 20 dBm. If properly limiting transmissions to 400 milliseconds, a 1% duty-cycle means at most one message every 40 seconds. This shouldn't be a problem in practice, but buggy upper level software still might do things more rapidly.
243261

244262
<!-- there are links to the following section, so be careful when renaming -->
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
3+
* Copyright (c) 2018 Terry Moore, MCCI
4+
* Copyright (c) 2025 Tristan Webber, Shrunk Innovation Labs.
5+
*
6+
* Permission is hereby granted, free of charge, to anyone
7+
* obtaining a copy of this document and accompanying files,
8+
* to do whatever they want with them without any restriction,
9+
* including, but not limited to, copying, modification and redistribution.
10+
* NO WARRANTY OF ANY KIND IS PROVIDED.
11+
*
12+
* This example demonstrates how to manually configure a board
13+
* requiring advanced initialisation (https://github.com/mcci-catena/arduino-lmic/blob/master/doc/HOWTO-Manually-Configure.md#advanced-initialization).
14+
* This is required for boards that need some level of configuration
15+
* beyond a simple pinmap. For example, for modems wired to
16+
* directly control the RfSwitch, or to define the 'BUSY' pin for
17+
* SX126x series modems.
18+
*
19+
* If you find yourself needing to do a configuration in this way
20+
* for a commercially available development board, consider making
21+
* a PR to integrate your board to the library.
22+
*
23+
* Here, a Heltec Wireless Stick Lite V3 is configured manually
24+
* by creating a new class derived from
25+
* `Arduino_LMIC::HalConfiguration_t` to override default methods
26+
* of the base class and ensure proper operation of the SX1262
27+
* modem.
28+
*
29+
* This example is otherwise identical to the `ttn-otaa` example.
30+
*
31+
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
32+
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
33+
* violated by this sketch when left running for longer)!
34+
35+
* To use this sketch, first register your application and device with
36+
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
37+
* Multiple devices can use the same AppEUI, but each device has its own
38+
* DevEUI and AppKey.
39+
*
40+
* Do not forget to define the radio type correctly in
41+
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
42+
*
43+
*******************************************************************************/
44+
45+
#include <lmic.h>
46+
#include <hal/hal.h>
47+
#include <SPI.h>
48+
49+
//
50+
// For normal use, we require that you edit the sketch to replace FILLMEIN
51+
// with values assigned by the TTN console. However, for regression tests,
52+
// we want to be able to compile these scripts. The regression tests define
53+
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
54+
// working but innocuous value.
55+
//
56+
#ifdef COMPILE_REGRESSION_TEST
57+
# define FILLMEIN 0
58+
#else
59+
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
60+
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
61+
#endif
62+
63+
// This EUI must be in little-endian format, so least-significant-byte
64+
// first. When copying an EUI from ttnctl output, this means to reverse
65+
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
66+
// 0x70.
67+
static const u1_t PROGMEM APPEUI[8]={ FILLMEIN };
68+
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
69+
70+
// This should also be in little endian format, see above.
71+
static const u1_t PROGMEM DEVEUI[8]={ FILLMEIN };
72+
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
73+
74+
// This key should be in big endian format (or, since it is not really a
75+
// number but a block of memory, endianness does not really apply). In
76+
// practice, a key taken from ttnctl can be copied as-is.
77+
static const u1_t PROGMEM APPKEY[16] = { FILLMEIN };
78+
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
79+
80+
static uint8_t mydata[] = "Hello, world!";
81+
static osjob_t sendjob;
82+
83+
// Schedule TX every this many seconds (might become longer due to duty
84+
// cycle limitations).
85+
const unsigned TX_INTERVAL = 60;
86+
87+
// Advanced HalConfiguration
88+
// Example is for a Heltec Wireless Stick Lite V3
89+
class cHalConfiguration_t: public Arduino_LMIC::HalConfiguration_t
90+
{
91+
public:
92+
// All SX126x series modems need the busy pin to be defined
93+
// by overriding the `queryBusyPin()` method.
94+
virtual u1_t queryBusyPin(void) override { return 13; };
95+
96+
// SX126x series modems can be wired to use a DC-DC or LDO
97+
// voltage regulator. Configure DC-DC by overriding this method
98+
virtual bool queryUsingDcdc(void) override { return true; };
99+
100+
// SX126x series modems can be wired to so that DIO2
101+
// controls an external RF switch.
102+
virtual bool queryUsingDIO2AsRfSwitch(void) override { return true; };
103+
104+
// Some modems switch a TCXO using DIO3. Configure by
105+
// overriding this method.
106+
virtual bool queryUsingDIO3AsTCXOSwitch(void) override { return true; };
107+
};
108+
109+
cHalConfiguration_t myConfig;
110+
111+
// Pin mapping
112+
const lmic_pinmap lmic_pins = {
113+
.nss = 8,
114+
.rxtx = LMIC_UNUSED_PIN,
115+
.rst = 12,
116+
.dio = {14, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN},
117+
.rxtx_rx_active = 0,
118+
.rssi_cal = 10,
119+
.spi_freq = 8000000,
120+
// Advanced configurations are passed to the pinmap via pConfig
121+
.pConfig = &myConfig,
122+
};
123+
124+
void printHex2(unsigned v) {
125+
v &= 0xff;
126+
if (v < 16)
127+
Serial.print('0');
128+
Serial.print(v, HEX);
129+
}
130+
131+
void onEvent (ev_t ev) {
132+
Serial.print(os_getTime());
133+
Serial.print(": ");
134+
switch(ev) {
135+
case EV_SCAN_TIMEOUT:
136+
Serial.println(F("EV_SCAN_TIMEOUT"));
137+
break;
138+
case EV_BEACON_FOUND:
139+
Serial.println(F("EV_BEACON_FOUND"));
140+
break;
141+
case EV_BEACON_MISSED:
142+
Serial.println(F("EV_BEACON_MISSED"));
143+
break;
144+
case EV_BEACON_TRACKED:
145+
Serial.println(F("EV_BEACON_TRACKED"));
146+
break;
147+
case EV_JOINING:
148+
Serial.println(F("EV_JOINING"));
149+
break;
150+
case EV_JOINED:
151+
Serial.println(F("EV_JOINED"));
152+
{
153+
u4_t netid = 0;
154+
devaddr_t devaddr = 0;
155+
u1_t nwkKey[16];
156+
u1_t artKey[16];
157+
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
158+
Serial.print("netid: ");
159+
Serial.println(netid, DEC);
160+
Serial.print("devaddr: ");
161+
Serial.println(devaddr, HEX);
162+
Serial.print("AppSKey: ");
163+
for (size_t i=0; i<sizeof(artKey); ++i) {
164+
if (i != 0)
165+
Serial.print("-");
166+
printHex2(artKey[i]);
167+
}
168+
Serial.println("");
169+
Serial.print("NwkSKey: ");
170+
for (size_t i=0; i<sizeof(nwkKey); ++i) {
171+
if (i != 0)
172+
Serial.print("-");
173+
printHex2(nwkKey[i]);
174+
}
175+
Serial.println();
176+
}
177+
// Disable link check validation (automatically enabled
178+
// during join, but because slow data rates change max TX
179+
// size, we don't use it in this example.
180+
LMIC_setLinkCheckMode(0);
181+
break;
182+
/*
183+
|| This event is defined but not used in the code. No
184+
|| point in wasting codespace on it.
185+
||
186+
|| case EV_RFU1:
187+
|| Serial.println(F("EV_RFU1"));
188+
|| break;
189+
*/
190+
case EV_JOIN_FAILED:
191+
Serial.println(F("EV_JOIN_FAILED"));
192+
break;
193+
case EV_REJOIN_FAILED:
194+
Serial.println(F("EV_REJOIN_FAILED"));
195+
break;
196+
case EV_TXCOMPLETE:
197+
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
198+
if (LMIC.txrxFlags & TXRX_ACK)
199+
Serial.println(F("Received ack"));
200+
if (LMIC.dataLen) {
201+
Serial.print(F("Received "));
202+
Serial.print(LMIC.dataLen);
203+
Serial.println(F(" bytes of payload"));
204+
}
205+
// Schedule next transmission
206+
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
207+
break;
208+
case EV_LOST_TSYNC:
209+
Serial.println(F("EV_LOST_TSYNC"));
210+
break;
211+
case EV_RESET:
212+
Serial.println(F("EV_RESET"));
213+
break;
214+
case EV_RXCOMPLETE:
215+
// data received in ping slot
216+
Serial.println(F("EV_RXCOMPLETE"));
217+
break;
218+
case EV_LINK_DEAD:
219+
Serial.println(F("EV_LINK_DEAD"));
220+
break;
221+
case EV_LINK_ALIVE:
222+
Serial.println(F("EV_LINK_ALIVE"));
223+
break;
224+
/*
225+
|| This event is defined but not used in the code. No
226+
|| point in wasting codespace on it.
227+
||
228+
|| case EV_SCAN_FOUND:
229+
|| Serial.println(F("EV_SCAN_FOUND"));
230+
|| break;
231+
*/
232+
case EV_TXSTART:
233+
Serial.println(F("EV_TXSTART"));
234+
break;
235+
case EV_TXCANCELED:
236+
Serial.println(F("EV_TXCANCELED"));
237+
break;
238+
case EV_RXSTART:
239+
/* do not print anything -- it wrecks timing */
240+
break;
241+
case EV_JOIN_TXCOMPLETE:
242+
Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
243+
break;
244+
245+
default:
246+
Serial.print(F("Unknown event: "));
247+
Serial.println((unsigned) ev);
248+
break;
249+
}
250+
}
251+
252+
void do_send(osjob_t* j){
253+
// Check if there is not a current TX/RX job running
254+
if (LMIC.opmode & OP_TXRXPEND) {
255+
Serial.println(F("OP_TXRXPEND, not sending"));
256+
} else {
257+
// Prepare upstream data transmission at the next possible time.
258+
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
259+
Serial.println(F("Packet queued"));
260+
}
261+
// Next TX is scheduled after TX_COMPLETE event.
262+
}
263+
264+
void setup() {
265+
Serial.begin(9600);
266+
Serial.println(F("Starting"));
267+
268+
#ifdef VCC_ENABLE
269+
// For Pinoccio Scout boards
270+
pinMode(VCC_ENABLE, OUTPUT);
271+
digitalWrite(VCC_ENABLE, HIGH);
272+
delay(1000);
273+
#endif
274+
275+
// LMIC init
276+
os_init();
277+
// Reset the MAC state. Session and pending data transfers will be discarded.
278+
LMIC_reset();
279+
280+
// Start job (sending automatically starts OTAA too)
281+
do_send(&sendjob);
282+
}
283+
284+
void loop() {
285+
os_runloop_once();
286+
}

0 commit comments

Comments
 (0)