Skip to content

Commit 453cb4e

Browse files
authored
Merge pull request #973 from mcci-catena/issue969
Address various issues related to SX126x support
2 parents f655520 + 975abae commit 453cb4e

File tree

8 files changed

+300
-277
lines changed

8 files changed

+300
-277
lines changed

README.md

Lines changed: 24 additions & 265 deletions
Large diffs are not rendered by default.

doc/HOWTO-Manually-Configure.md

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# Manual configuration
2+
3+
If your desired transceiver board is not pre-integrated, you need to provide the library with the required information.
4+
5+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), you can skip this document.
6+
7+
## Transceiver Wiring
8+
9+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
10+
11+
You may need to wire up your transceiver. The exact
12+
connections are a bit dependent on the transceiver board and Arduino
13+
used, so this section tries to explain what each connection is for and
14+
in what cases it is (not) required.
15+
16+
Note that the SX127x module runs at 3.3V and likely does not like 5V on
17+
its pins (though the datasheet is not say anything about this, and my
18+
transceiver did not obviously break after accidentally using 5V I/O for
19+
a few hours). To be safe, make sure to use a level shifter, or an
20+
Arduino running at 3.3V. The Semtech evaluation board has 100 ohm resistors in
21+
series with all data lines that might prevent damage, but I would not
22+
count on that.
23+
24+
You must select the proper radio for your board in [`lmic_project_config.h`](../project_config/lmic_project_config.h), using a `#define` to define the appropriate symbol chosen from `CFG_sx1272_radio`, `CFG_sx1276_radio`, `CFG_sx1261_radio` or `CFG_sx1262_radio`.
25+
26+
## Power
27+
28+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
29+
30+
The SX127x transceivers need a supply voltage between 1.8V and 3.9V.
31+
Using a 3.3V supply is typical. Some modules have a single power pin
32+
(like the HopeRF modules, labeled 3.3V) but others expose multiple power
33+
pins for different parts (like the Semtech evaluation board that has
34+
`VDD_RF`, `VDD_ANA` and `VDD_FEM`), which can all be connected together.
35+
Any *GND* pins need to be connected to the Arduino *GND* pin(s).
36+
37+
### SPI
38+
39+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
40+
41+
The primary way of communicating with the transceiver is through SPI
42+
(Serial Peripheral Interface). This uses four pins: MOSI, MISO, SCK and
43+
SS. The former three need to be directly connected: so MOSI to MOSI,
44+
MISO to MISO, SCK to SCK. Where these pins are located on your Arduino
45+
varies, see for example the "Connections" section of the [Arduino SPI
46+
documentation](SPI).
47+
48+
The SS (slave select) connection is a bit more flexible. On the SPI
49+
slave side (the transceiver), this must be connected to the pin
50+
(typically) labeled *NSS*. On the SPI master (Arduino) side, this pin
51+
can connect to any I/O pin. Most Arduinos also have a pin labeled "SS",
52+
but this is only relevant when the Arduino works as an SPI slave, which
53+
is not the case here. Whatever pin you pick, you need to tell the
54+
library what pin you used through the pin mapping (see [below](#pin-mapping)).
55+
56+
[SPI]: https://www.arduino.cc/en/Reference/SPI
57+
58+
## DIO pins
59+
60+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
61+
62+
The DIO (digital I/O) pins on the SX127x can be configured
63+
for various functions. The LMIC library uses them to get instant status
64+
information from the transceiver. For example, when a LoRa transmission
65+
starts, the DIO0 pin is configured as a TxDone output. When the
66+
transmission is complete, the DIO0 pin is made high by the transceiver,
67+
which can be detected by the LMIC library.
68+
69+
The LMIC library needs only access to DIO0, DIO1 and DIO2, the other
70+
DIOx pins can be left disconnected. On the Arduino side, they can
71+
connect to any I/O pin. If interrupts are used, the accuracy of timing
72+
will be improved, particularly the rest of your `loop()` function has
73+
lengthy calculations; but in that case, the enabled DIO pins must all
74+
support rising-edge interrupts. See the [Timing](#timing) section below.
75+
76+
In LoRa mode the DIO pins are used as follows:
77+
78+
* DIO0: TxDone and RxDone
79+
* DIO1: RxTimeout
80+
81+
In FSK mode they are used as follows::
82+
83+
* DIO0: PayloadReady and PacketSent
84+
* DIO2: TimeOut
85+
86+
Both modes need only 2 pins, but the transceiver does not allow mapping
87+
them in such a way that all needed interrupts map to the same 2 pins.
88+
So, if both LoRa and FSK modes are used, all three pins must be
89+
connected.
90+
91+
The pins used on the Arduino side should be configured in the pin
92+
mapping in your sketch, by setting the values of `lmic_pinmap::dio[0]`, `[1]`, and `[2]` (see [below](#pin-mapping)).
93+
94+
## Reset
95+
96+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
97+
98+
The transceiver has a reset pin that can be used to explicitly reset
99+
it. The LMIC library uses this to ensure the chip is in a consistent
100+
state at startup. In practice, this pin can be left disconnected, since
101+
the transceiver will already be in a sane state on power-on, but
102+
connecting it might prevent problems in some cases.
103+
104+
On the Arduino side, any I/O pin can be used. The pin number used must
105+
be configured in the pin mapping `lmic_pinmap::rst` field (see [below](#pin-mapping)).
106+
107+
## RXTX
108+
109+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
110+
111+
112+
The transceiver contains two separate antenna connections: One for RX
113+
and one for TX. A typical transceiver board contains an antenna switch
114+
chip, that allows switching a single antenna between these RX and TX
115+
connections. Such a antenna switcher can typically be told what
116+
position it should be through an input pin, often labeled *RXTX*.
117+
118+
The easiest way to control the antenna switch is to use the *RXTX* pin
119+
on the SX127x transceiver. This pin is automatically set high during TX
120+
and low during RX. For example, the HopeRF boards seem to have this
121+
connection in place, so they do not expose any *RXTX* pins and the pin
122+
can be marked as unused in the pin mapping.
123+
124+
Some boards do expose the antenna switcher pin, and sometimes also the
125+
SX127x *RXTX* pin. For example, the SX1272 evaluation board calls the
126+
former *FEM_CTX* and the latter *RXTX*. Again, simply connecting these
127+
together with a jumper wire is the easiest solution.
128+
129+
Alternatively, or if the SX127x *RXTX* pin is not available, LMIC can be
130+
configured to control the antenna switch. Connect the antenna switch
131+
control pin (e.g. *FEM_CTX* on the Semtech evaluation board) to any I/O
132+
pin on the Arduino side, and configure the pin used in the pin map (see
133+
[below](#pin-mapping)).
134+
135+
The configuration entry `lmic_pinmap::rxtx` configures the pin to be used for the *RXTX* control function, in terms of the Arduino `wire.h` digital pin number. If set to `LMIC_UNUSED_PIN`, then the library assumes that software does not need to control the antenna switch.
136+
137+
## RXTX Polarity
138+
139+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
140+
141+
If an external switch is used, you also must specify the polarity. Some modules want *RXTX* to be high for transmit, low for receive; Others want it to be low for transmit, high for receive. The Murata module, for example, requires that *RXTX* be *high* for receive, *low* for transmit.
142+
143+
The configuration entry `lmic_pinmap::rxtx_rx_active` should be set to the state to be written to the *RXTX* pin to make the receiver active. The opposite state is written to make the transmitter active. If `lmic_pinmap::rxtx` is `LMIC_UNUSED_PIN`, then the value of `lmic_pinmap::rxtx_rx_active` is ignored.
144+
145+
## Pin mapping
146+
147+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
148+
149+
Refer to the documentation on your board for the required settings.
150+
151+
Remember, for pre-integrated boards, you don't worry about this.
152+
153+
We have details for the following manually-configured boards here:
154+
155+
- [LoRa Nexus by Ideetron](#lora-nexus-by-ideetron)
156+
157+
If your board is not configured, you need at least to provide your own `lmic_pinmap`. As described above, a variety of configurations are possible. To tell the LMIC library how your board is configured, you must declare a variable containing a pin mapping struct in your sketch file. If you call `os_init()` to initialize the LMIC, you must name this structure `lmic_pins`. If you call `os_init_ex()`, you may name the structure what you like, but you pass a pointer as the parameter to `os_init_ex()`.
158+
159+
Here's an example of a simple initialization:
160+
161+
```c++
162+
lmic_pinmap lmic_pins = {
163+
.nss = 6,
164+
.rxtx = LMIC_UNUSED_PIN,
165+
.rst = 5,
166+
.dio = {2, 3, 4},
167+
// optional: set polarity of rxtx pin.
168+
.rxtx_rx_active = 0,
169+
// optional: set RSSI cal for listen-before-talk
170+
// this value is in dB, and is added to RSSI
171+
// measured prior to decision.
172+
// Must include noise guardband! Ignored in US,
173+
// EU, IN, other markets where LBT is not required.
174+
.rssi_cal = 0,
175+
// optional: override LMIC_SPI_FREQ if non-zero
176+
.spi_freq = 0,
177+
};
178+
```
179+
180+
The names refer to the pins on the transceiver side, the numbers refer
181+
to the Arduino pin numbers (to use the analog pins, use constants like
182+
`A0`). For the DIO pins, the three numbers refer to DIO0, DIO1 and DIO2
183+
respectively. Any pins that are not needed should be specified as
184+
`LMIC_UNUSED_PIN`. The NSS and dio0 pins are required. The others can
185+
potentially left out (depending on the environments and requirements,
186+
see the notes above for when a pin can or cannot be left out).
187+
188+
## Advanced initialization
189+
190+
> If you're using a [pre-integrated board](../README.md#pre-integrated-boards), ignore this section.
191+
192+
In some boards require much more advanced management. The LMIC has a very flexible framework to support this, but it requires you to do some C++ work.
193+
194+
1. You must define a new class derived from `Arduino_LMIC::HalConfiguration_t`. (We'll call this `cMyHalConfiguration_t`).
195+
196+
2. This class *may* define overrides for several methods (discussed below).
197+
198+
3. You must create an instance of your class, e.g.
199+
200+
```c++
201+
cMyHalConfiguration_t myHalConfigInstance;
202+
```
203+
204+
4. You add another entry in your `lmic_pinmap`, `pConfig = &myHalConfigInstance`, to link your pin-map to your object.
205+
206+
The full example looks like this:
207+
208+
```c++
209+
class cMyHalConfiguration_t : public Arduino_LMIC::HalConfiguration_t
210+
{
211+
public:
212+
// ...
213+
// put your method function override declarations here.
214+
215+
// this example uses RFO at 10 dBm or less, PA_BOOST up to 17 dBm,
216+
// or the high-power mode above 17 dBm. In other words, it lets the
217+
// LMIC-determined policy determine what's to be done.
218+
219+
virtual TxPowerPolicy_t getTxPowerPolicy(
220+
TxPowerPolicy_t policy,
221+
int8_t requestedPower,
222+
uint32_t frequency
223+
) override
224+
{
225+
return policy;
226+
}
227+
};
228+
```
229+
230+
## HalConfiguration_t methods
231+
232+
- `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.
233+
234+
- `void begin(void)` is called during initialization, and is your code's chance to do any early setup.
235+
236+
- `void end(void)` is (to be) called during late shutdown. (Late shutdown is not implemented yet; but we wanted to add the API for consistency.)
237+
238+
- `bool queryUsingTcxo(void)` shall return `true` if the module uses a TCXO; `false` otherwise.
239+
240+
- `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.)
241+
242+
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.
243+
244+
<!-- there are links to the following section, so be careful when renaming -->
245+
## Example: LoRa Nexus by Ideetron
246+
247+
This board uses the following pin mapping:
248+
249+
```c++
250+
const lmic_pinmap lmic_pins = {
251+
.nss = 10,
252+
.rxtx = LMIC_UNUSED_PIN,
253+
.rst = LMIC_UNUSED_PIN, // hardwired to AtMega RESET
254+
.dio = {4, 5, 7},
255+
};
256+
```

src/hal/hal.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,16 @@ static void hal_spi_init () {
192192
}
193193

194194
#if (defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio))
195-
bit_t is_busy() {
195+
bit_t hal_radio_spi_is_busy() {
196196
// SX126x uses BUSY pin
197197
return digitalRead(pHalConfig->queryBusyPin()) ? true : false;
198198
}
199-
#endif
199+
#else
200+
// supply a definition just in case, because the declaration is not conditional
201+
bit_t hal_radio_spi_is_busy() {
202+
return false;
203+
}
204+
#endif // (defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio))
200205

201206
static void hal_spi_trx(u1_t cmd, u1_t* buf, size_t len, bit_t is_read) {
202207
uint32_t spi_freq;
@@ -211,7 +216,7 @@ static void hal_spi_trx(u1_t cmd, u1_t* buf, size_t len, bit_t is_read) {
211216

212217
// SX126x modems use BUSY pin. Only interact with SPI when BUSY goes LOW
213218
#if (defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio))
214-
while (is_busy());
219+
while (hal_radio_spi_is_busy());
215220
#endif
216221

217222
SPI.transfer(cmd);
@@ -248,7 +253,7 @@ void hal_spi_read_sx126x(u1_t cmd, u1_t* addr, size_t addr_len, u1_t* buf, size_
248253
SPI.beginTransaction(settings);
249254
digitalWrite(nss, 0);
250255

251-
while (is_busy());
256+
while (hal_radio_spi_is_busy());
252257

253258
SPI.transfer(cmd);
254259

src/lmic/hal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ uint8_t hal_getTxPowerPolicy(
191191

192192
void hal_pollPendingIRQs_helper();
193193
void hal_processPendingIRQs(void);
194-
bit_t is_busy();
194+
bit_t hal_radio_spi_is_busy();
195195

196196
/// \brief check for any pending interrupts: stub if interrupts are enabled.
197197
static inline void hal_pollPendingIRQs(void)

src/lmic/lmic.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2570,7 +2570,10 @@ static void engineUpdate_inner (void) {
25702570

25712571
if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) {
25722572
// Assuming txChnl points to channel which first becomes available again.
2573+
2574+
// set jacc true if we'll transmit a join-accept rather than user data
25732575
bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0);
2576+
25742577
// Find next suitable channel and return availability time
25752578
if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) {
25762579
txbeg = LMIC.txend = LMICbandplan_nextTx(now);

src/lmic/lmic.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ extern "C"{
106106
((((major)*UINT32_C(1)) << 24) | (((minor)*UINT32_C(1)) << 16) | (((patch)*UINT32_C(1)) << 8) | (((local)*UINT32_C(1)) << 0))
107107

108108
#define ARDUINO_LMIC_VERSION \
109-
ARDUINO_LMIC_VERSION_CALC(4, 2, 0, 2) /* 4.2.0-pre2 */
109+
ARDUINO_LMIC_VERSION_CALC(4, 2, 0, 3) /* 4.2.0-pre3 */
110110

111111
#define ARDUINO_LMIC_VERSION_GET_MAJOR(v) \
112112
((((v)*UINT32_C(1)) >> 24u) & 0xFFu)

src/lmic/radio_sx126x.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
#include "lmic.h"
3434

35-
#if (CFG_sx1261_radio || CFG_sx1262_radio)
35+
#if defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio)
3636
// This driver is based on Rev. 2.1 of the Semtech SX1261/2 Data Sheet DS.SX1261-2.W.APP
3737
// ----------------------------------------
3838
// Command Mapping ** Chapter 11 List of Commands
@@ -766,7 +766,7 @@ void radio_config(void) {
766766
// Perform necessary operations from STDBY_RC mode
767767
if ((getStatus() | SX126x_GETSTATUS_CHIPMODE_MASK) != SX126x_CHIPMODE_STDBY_RC) {
768768
// Assume we've woken from sleep
769-
while (is_busy());
769+
while (hal_radio_spi_is_busy());
770770
setStandby(STDBY_RC);
771771
}
772772

@@ -1145,7 +1145,7 @@ int radio_init(void) {
11451145
hal_waitUntil(os_getTime()+ms2osticks(1)); // wait >100us
11461146
hal_pin_rst(2); // configure RST pin floating!
11471147
hal_waitUntil(os_getTime()+ms2osticks(5)); // wait 5ms
1148-
while(is_busy()); // wait for busy pin to go low
1148+
while(hal_radio_spi_is_busy()); // wait for busy pin to go low
11491149

11501150
// Check default LoRa sync word to verify the reset was successful
11511151
u1_t syncWordMSB = readRegister(LoRaSyncWordMSB);
@@ -1465,4 +1465,4 @@ void os_radio(u1_t mode) {
14651465
ostime_t os_getRadioRxRampup(void) {
14661466
return RX_RAMPUP_DEFAULT + us2osticks(12480); // SX126x is 780 ticks slower than SX127x to wake from sleep @ 240MHz
14671467
}
1468-
#endif
1468+
#endif // defined(CFG_sx1261_radio) || defined(CFG_sx1262_radio)

src/lmic/radio_sx127x.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#define LMIC_DR_LEGACY 0
3232

3333
#include "lmic.h"
34-
#if (CFG_sx1272_radio || CFG_sx1276_radio)
34+
#if defined(CFG_sx1272_radio) || defined(CFG_sx1276_radio)
3535
// ----------------------------------------
3636
// Registers Mapping
3737
// // -type- 1272 vs 1276
@@ -1441,4 +1441,4 @@ void os_radio (u1_t mode) {
14411441
ostime_t os_getRadioRxRampup (void) {
14421442
return RX_RAMPUP_DEFAULT;
14431443
}
1444-
#endif
1444+
#endif // defined(CFG_sx1272_radio) || defined(CFG_sx1276_radio)

0 commit comments

Comments
 (0)