While doing research for the upcoming LoRa: Writing our own Protocol - Part 3 - Protocol Draft I discovered some issues that will be a huge headache in the future. The LoRaWAN Semtech Packet Forwarder Protocol is nice and easy to implement.
Unfortunately the LoRaWAN Specifications1 for downstream messages are quite annoying and strict if it comes to timing. Receiving on 8 Channels simultaneously is an awesome feature of the LoRaWAN concentrator, but sending messages is not stable enough for my experiments and future ideas.
I need something that reacts quickly to incoming message (e.g. via HTTP or MQTT) and sends it out on the downstream frequency immediately. Literally no time for time restrictions.
Sending downstream packets is usually done on only one frequency, I can use any LoRa module to do that and since we also know by now how LoRaWAN Gateways and LoRaWAN packets works under the hood. It should be relatively easy to implement and a more reliable approach then LoRaWAN’s slot timing.
Hardware
The LoRaWAN downstream service, I have in my mind, is very easy and should work with an Arduino Uno, Ethernet Shield and a LoRa breakout board. I have a box full of cheap Arduino Ethernet Shield clones that I used in my older adventures but I don’t have any LoRa modules.
But what I do have, is another box full of my favorite LoRa boards. By now it shouldn’t suprise you which LoRa board I am talking about. Of course it’s my lovely favorite TTGO T-Beam ❤️ LoRa boards.
But wait, there is a tiny issue, I want to avoid WiFi as much as I can. The device should run in an outdoor enclosure and be plugged into a network switch.
I really want to use an ESP32 over an AVR chip like ATmega328p, but at the same time I need Ethernet peripherals. I would use another favorite ESP32 board of mine called WT32-ETH-01 that comes with Ethernet, but as I mentioned before, I don’t have a LoRa module laying around.
And No! I really don’t wanna use something like Arduino Uno + Ethernet Shield -> Serial with Logic Level Converter -> TTGO T-Beam
.
I want Ethernet on the TTGO T-Beam.
Software
The majority of ESP32 chips have an Ethernet Controller2 but how about using Ethernet via SPI on a TTGO T-Beam?
This shouldn’t be a big deal, right? After all the ESP32 Chips have multiple Serial Peripheral Interfaces and you can also use mutiple devices on the same SPI bus.
I took inspiration from the Renzo Mischianti’s Blog3 and first tried an ESP32 Dev Board with a WIZnet W5500 Lite module. It works perfectly fine. (It is very important to use external 3.3V power supply for this module!)
But of course is not that easy on the T-Beam. On the left and right side of T-Beam there isn’t anyplace where the default SPI pins are exposed.
In other words, only two options left: Soldering jumper cables on the LoRa Chips solder pads or try to find out which GPIO pins can be used as (separate) SPI.
I dug into the pin_arduino.h
of the T-Beam and got even more confused. So I asked LilyGo on Twitter4 and they suggested that I use pins 13
, 14
, 25
, 33
, 32
and 35
for SPI5.
After trying all evening, and even contacting the LilyGo Developer, I was about to give up and try to solder the W5500 Lite’s SPI pins on the LoRa Chips SPI bus.
I passed a GitHub Gist to the Developer and to a friend and went sleep. (I really try hard to sleep and switch off my mind this time!)
Next day: The solution was easier then I thought!
Demo
Here is the source code of a PoC how to use the Arduino Ethernet
, arduino-LoRa
and PubSubClient
library simultaneously. The Device sends a test packet every 15 seconds via LoRa and it listens to the topic inTopic
. MQTT packets received on the inTopic
topic gets transmitted via LoRa and a copy of the content is send back to the topic outTopic
.
You can also download the source code on GitHub.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include <Arduino.h>
#include <SPI.h>
#include <Ethernet.h>
#include <LoRa.h>
#include <PubSubClient.h>
#define LORA_SCK 5
#define LORA_MISO 19
#define LORA_MOSI 27
#define LORA_CS 18
#define LORA_RST 23
#define LORA_DIO0 26
#define LORA_FREQ 868000000
#define LORA_BAND 125E5
#define LORA_SF 8
#define LORA_RATE 4
#define LORA_PREA 8
#define LORA_SYNC 0x34
#define ETH_SCK 14
#define ETH_MISO 25
#define ETH_MOSI 13
#define ETH_CS 15
#define IPADDR 192,168,23,100
#define IPMASK 255,255,255,0
#define DNS 192,168,23,1
#define GATEWAY 192,168,23,1
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
unsigned long previousMillis = 0;
String clientId = "T-BEAM-W5500";
IPAddress ip(IPADDR);
IPAddress sn(IPMASK);
IPAddress dns(DNS);
IPAddress gw(GATEWAY);
IPAddress server(GATEWAY);
void callback(char* topic, byte* payload, unsigned int length);
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
SPIClass LoRa_SPI = SPIClass(HSPI);
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// do some length checks here
byte* p = (byte*)malloc(length);
memcpy(p, payload, length);
// echo back the packet on outTopic
client.publish("outTopic", p, length);
// send received MQTT packet via LoRa
LoRa.beginPacket();
LoRa.write(p, length);
LoRa.endPacket();
// do not forget to free allocated memory
free(p);
}
void initLoRa(){
LoRa_SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
// Important: Pass the LoRa_SPI to the setSPI function!
LoRa.setSPI(LoRa_SPI);
LoRa.setPins(LORA_CS, LORA_RST, LORA_DIO0);
LoRa.setFrequency(LORA_FREQ);
LoRa.setSignalBandwidth(LORA_BAND);
if (!LoRa.begin(LORA_FREQ)) {
while (true) {
Serial.println("Starting LoRa failed!");
delay(5000);
}
} else {
Serial.println("LoRa OK");
}
// Settings for LoRaWAN compatible LoRa packets
LoRa.enableCrc();
LoRa.setCodingRate4(LORA_RATE);
LoRa.setPreambleLength(LORA_PREA);
LoRa.setSpreadingFactor(LORA_SF);
LoRa.setSyncWord(LORA_SYNC);
LoRa.enableInvertIQ(); // Prevent other LoRaWAN Gateways from receiving downstream messages
}
void initEthernet() {
Serial.println("Begin Ethernet");
SPI.begin(ETH_SCK, ETH_MISO, ETH_MOSI, ETH_CS);
Ethernet.init(ETH_CS);
Ethernet.begin(mac, ip, dns, gw, sn);
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
while (true) {
Serial.println("Ethernet shield was not found.");
delay(5000);
}
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}
Serial.println("Ethernet Successfully Initialized");
if (client.connect(clientId.c_str())) {
client.publish("outTopic","Hello Technopolis Citizen");
client.subscribe("inTopic");
}
}
void reconnect() {
if (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(clientId.c_str())) {
Serial.println("connected");
client.publish("outTopic","Hello Technopolis Citizen");
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
}
}
}
void setup() {
Serial.begin(115200);
initEthernet();
initLoRa();
}
void loop() {
unsigned long currentMillis = millis();
if (!client.connected()) {
reconnect();
}
client.loop();
if (currentMillis - previousMillis >= 15000) {
Serial.print("Sending Test LoRa packet... ");
LoRa.beginPacket();
LoRa.print("OK");
LoRa.endPacket();
Serial.println("OK");
// uncomment if you want to send MQTT test packet too
// if (Ethernet.linkStatus() == LinkON) {
// if (client.connect(clientId.c_str())) {
// client.publish("outTopic", "OK");
// }
// }
previousMillis = currentMillis;
}
}
Sources
Shout-out and thanks to LilyGo and especially to my friend Kongduino. He quickly spotted that I didn’t use the new SPI interface after declaring it, so the library LoRa.h
kept using the default SPI pins.