Home PlatformIO: Ethernet + LoRa - TTGO T-Beam WIZnet W5500 Lite
Post
Cancel

PlatformIO: Ethernet + LoRa - TTGO T-Beam WIZnet W5500 Lite

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

TTGO T-Beam W5500 Lite

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

ESP32 DOIT DevKit v1 w5500 wiring breadboard

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.

TTGO T-Beam W5500 Lite

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.

PlatformIO: Self-destructing Arduino ESP32 Firmware

Why I pay monthly 1,247.40 for Technopolis.tv and I'm fine with that