Using LoRa with the EByte E220, E22 and E32 series

About this Post

This article is once again about radio, more precisely about LoRa technology using the example of the Ebyte E220, E22 and E32 module series. The example sketches are written for the E220 modules, but can be transferred relatively easily, especially as I list the essential points that need to be adapted. Please forgive me for not having written three versions of each sketch.

Here is an overview of the post:

Legal – please note!

Important to know:

  • Radio transmission is regulated by law in almost every country in the world, e.g. regarding the permitted frequencies, the transmission power or the duty cycle (percentage of the actual transmission time).
  • Not everything you can buy in your country can be used without restrictions – and some things can’t be used at all!

In Germany, the rules for radio are set by the Federal Network Agency. You can find the relevant details here in the section “Funkanlagen mit geringer Reichweite”. Hobbyists mainly use the frequencies around 433 MHZ, 868 MHz, 915 MHz and 2.4 GHz – but they are not free of restrictions. You are responsible for complying with the regulations in your country.

What is LoRa, LoRaWAN and LPWAN?

If you are dealing with LoRa, you will also come across the terms LoRaWAN and LPWAN. LoRa stands for Long Range, i.e. it is a radio technology for comparatively long distances. LoRa (like Sigfox) belongs to the group of LPWAN radio technologies, which in turn is the abbreviation for Low-Power Wide Area Network. This name thus contains a second characteristic of this technology, namely its low-energy consumption. While LoRa refers to the special radio technology, LoRaWAN is a communication protocol based on it.

I am neither a physicist nor a radio engineer. That’s why I prefer to hold back on explaining exactly how LoRa works. Just this much: It is a special modulation technology that achieves the long-range and low-energy consumption. The data transfer rates are comparatively low.

LoRa Modules

Many different LoRa modules are offered in popular online stores such as Amazon or AliExpress. These also include ready-made solutions, e.g. ESP32-based boards with OLED displays. Originally, I had planned to give a broader overview of common modules and libraries. However, I then realized that the topic is simply too extensive to cover everything in one post. I will therefore initially limit myself to the E220, E22 and E32 series from Ebyte. They are close relatives that can be programmed in almost the same way. I may look at other modules in a later post.

Ebyte LoRa Modules

Overview

Ebyte, more precisely Chengdu Ebyte Electronic Technology Co. Ltd, offers various LoRa modules. If you are looking for specific models, take a look here. I have summarized the most important differences between the module series here:

Overview of the E220, E22 and E32 series
Overview of the E220, E22 and E32 series

The modules all have the same pinout and are very similar in operation.

An overview of the SX1262, SX1276 and SX1278 chips can be found here. All chips and modules have their advantages and disadvantages.

Naming

The modules are named according to the scheme Eaaa-bbbTccd, e.g. E220-900T22D. The parameters are: 

  • aaa: the “family name”,
  • bbb: represents the radio frequency,
  • cc: represents the signal strength in dBm,
  • d: stands for the design (DIP or SMD).

Here, as an example, is an overview of some representatives of the E220 series:

Model overview of the LoRa E220 series
Model overview of the LoRa E220 series (source: E220-900T22D data sheet)

Technical Data (Example E220-900T22D)

You can find data sheets and manuals on the Chengdu Ebyte website. As an example, I have the technical data of the E220-900T22D model for you here:

Technical data E220-900T22D LoRa module
Technical data E220-900T22D LoRa module (source: E220-900T22D data sheet)

I found a data sheet for the same module elsewhere, but with slightly different information on the optimum voltage supply:

Version 2 of the E220-900T22D LoRa module.
Version 2 of the E220-900T22D LoRa module.

If in doubt, try out whether you achieve better results with 5 volts. However, it is important to note that not all pins can handle 5 volts. For 5 volt MCUs, you should therefore use level shifters or voltage dividers.

Pinout LoRa E220, E22, E32 Series

Pinout LoRa E220 (identical to E22 and E32)
Pinout LoRa E220, E22 and E32
  • GND / VCC: Power supply
  • AUX: Indicates the status of the data buffers for sending and receiving and is used for the self-check.
  • RX / TX: Serial communication
  • M0 / M1 (E220): Control of the four operating modes:
    1. M0 = LOW,  M1 = LOW: Normal mode
    2. M0 = HIGH, M1 = LOW: WOR Transmitter
    3. M0 = LOW,  M1 = HIGH: WOR Receiver
    4. M0 = HIGH, M1 = HIGH: Deep sleep
  • M0 /M1 (E22): Control  of the four operating modes:
    1. M0 = LOW,  M1 = LOW: Normal mode
    2. M0 = HIGH, M1 = LOW: WOR mode (transmitter/receiver)
    3. M0 = LOW,  M1 = HIGH: Configuration mode
    4. M0 = HIGH, M1 = HIGH: Deep sleep
  • M0 / M1 (E32): Control of the four operating modes:
    1. M0 = LOW,  M1 = LOW: Normal mode
    2. M0 = HIGH, M1 = LOW: Wake-up mode (transmitter)
    3. M0 = LOW,  M1 = HIGH: Power saving
    4. M0 = HIGH, M1 = HIGH: Sleep

Libraries for the Ebyte E220, E22 and E32 Series

I selected the libraries of Renzo Mischianti to control the modules. You can download them here from GitHub:

… or install via the library manager of the Arduino IDE.

In addition, the industrious Renzo Mischianti has created a multipart tutorial for each of the module series. Here are the links to the first parts: E220-Tutorial, E22-Tutorial, E32-Tutorial.

My motivation for writing my own article was to summarize things a little more compactly and to compare the module series. However, it is definitely worth taking a look at the tutorials. They offer a lot of additional information, such as example sketches and circuit diagrams for other boards.

The tutorials and libraries for the various series are structured in the same way. This means you can quickly find your way around if you change the module series.

Circuit for the Arduino Nano

In this article, I use the classic Arduino Nano as the controlling board. I connected it to the E220 module as follows (identical for E22 and E32):

Connection of the E220 module to the Arduino Nano
Connection of the E220 module to the Arduino Nano

A few comments on this:

  • If you do not want to use the WOR (Wake On Radio) or power-down modes, you can connect M0 and M1 to GND.
  • In principle, you can also leave AUX unconnected. In this case, the microcontroller does not know when the data transfer is complete, but the library provides a sufficient waiting time.
  • When using a 5 volt board, you must use a level shifter or a voltage divider (e.g. 2 kΩ / 1 kΩ) for connecting M0, M1, RX to the board pins.
  • AUX, RX and TX require a pull-up resistor.
  • Example circuits for other MCU boards can be found in the tutorials by Renzo Mischianti.
  • The antennas for the E220-900T22D and E32-433T30D modules I used were special versions with a magnetic base for 868 MHz and 433 MHz. These are available for < €10 in online stores.

This is what my setup looked like on the breadboard:

LoRa E220 module connected to an Arduino Nano on the breadboard
LoRa E220 module connected to an Arduino Nano on the breadboard

How to make Settings

Configuration Sketch using the E220 Module Series

The libraries are equipped with separate example sketches for setting up and operating the modules. Use setConfiguration.ino to make the settings, and getConfiguration.ino to query them. These settings are retained even if the power supply is disconnected, provided you select the parameter WRITE_CFG_PWR_DWN_SAVE when saving the configuration (line 62 in the next sketch).

When you open setConfiguration.ino, you may be overwhelmed by its size. However, most of it is commented out, and many things are repeated. The upper section contains various examples of object creation for different boards. The middle section contains ready-made configuration examples that you can easily uncomment. At the end you will find the functions for outputting the settings. 

I have reduced setConfiguration.ino (E220 module) to the essentials for use on an Arduino Nano. But I have also added a few lines:

  • Lines 1 to 5 are used to output the correct frequency. Just uncomment the line with the correct frequency range.
  • Lines 6 and 7 are relevant for selecting the signal strength.
  • Line 8 ensures that the settings are output in detail.
  • Lines 57 and 58 allow you to encrypt your messages individually.
// #define FREQUENCY_433 // default value without set 
// #define FREQUENCY_170
// #define FREQUENCY_470
#define FREQUENCY_868
// #define FREQUENCY_915
// #define E220_22 // default value without set 
// #define E220_30 // uncomment in case you use an E220...T30D or E220...T30S
#define LoRa_E220_DEBUG  // for printing the settings
#include "LoRa_E220.h"

SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
// LoRa_E220 e220ttl(4, 5, 3, 7, 6); // alternative function to create the LoRa_E220 object

void printParameters(struct Configuration configuration);
void printModuleInformation(struct ModuleInformation moduleInformation);

void setup() {
  Serial.begin(9600);
  while(!Serial){};
  delay(500);

  Serial.println();


  // Startup all pins and UART
  e220ttl.begin();

  ResponseStructContainer c;
  c = e220ttl.getConfiguration();
  // It's important get configuration pointer before all other operation
  Configuration configuration = *(Configuration*) c.data;
  Serial.println(c.status.getResponseDescription());
  Serial.println(c.status.code);

  printParameters(configuration);

//	----------------------- DEFAULT TRANSPARENT ----------------------- 
  configuration.ADDL = 0x02;  // Low byte of address
  configuration.ADDH = 0x00; // High byte of address

  configuration.CHAN = 18; // 868 MHz for Exxx-900 modules, choose 23 for Exxx-400 to set 433 MHz 

  configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate
  configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate
  configuration.SPED.uartParity = MODE_00_8N1; // Parity bit

  configuration.OPTION.subPacketSetting = SPS_200_00; // Packet size
  configuration.OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; // Need to send special command
  configuration.OPTION.transmissionPower = POWER_22; // Device power

  configuration.TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; // Enable RSSI info
  configuration.TRANSMISSION_MODE.fixedTransmission = FT_TRANSPARENT_TRANSMISSION; // Transmission mode
  configuration.TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // Check interference
  configuration.TRANSMISSION_MODE.WORPeriod = WOR_2000_011; // WOR timing

  configuration.CRYPT.CRYPT_H = 0x00;  // encryption high byte, default: 0x00
  configuration.CRYPT.CRYPT_L = 0x00;  // encryption low byte, default: 0x00

  /* Set configuration changed and set to hold the configuration; chose 
WRITE_CFG_PWR_DWN_LOSE to not save the configuration permanently */
  ResponseStatus rs = e220ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE);
  Serial.println(rs.getResponseDescription());
  Serial.println(rs.code);

  c = e220ttl.getConfiguration();
  // It's important get configuration pointer before all other operation
  configuration = *(Configuration*) c.data;
  Serial.println(c.status.getResponseDescription());
  Serial.println(c.status.code);

  printParameters(configuration);
  c.close();
}

void loop() {

}
void printParameters(struct Configuration configuration) {
  DEBUG_PRINTLN("----------------------------------------");

  DEBUG_PRINT(F("HEAD : "));  DEBUG_PRINT(configuration.COMMAND, HEX);DEBUG_PRINT(" ");DEBUG_PRINT(configuration.STARTING_ADDRESS, HEX);DEBUG_PRINT(" ");DEBUG_PRINTLN(configuration.LENGHT, HEX);
  DEBUG_PRINTLN(F(" "));
  DEBUG_PRINT(F("AddH : "));  DEBUG_PRINTLN(configuration.ADDH, HEX);
  DEBUG_PRINT(F("AddL : "));  DEBUG_PRINTLN(configuration.ADDL, HEX);
  DEBUG_PRINTLN(F(" "));
  DEBUG_PRINT(F("Chan : "));  DEBUG_PRINT(configuration.CHAN, DEC); DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.getChannelDescription());
  DEBUG_PRINTLN(F(" "));
  DEBUG_PRINT(F("SpeedParityBit     : "));  DEBUG_PRINT(configuration.SPED.uartParity, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.SPED.getUARTParityDescription());
  DEBUG_PRINT(F("SpeedUARTDatte     : "));  DEBUG_PRINT(configuration.SPED.uartBaudRate, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.SPED.getUARTBaudRateDescription());
  DEBUG_PRINT(F("SpeedAirDataRate   : "));  DEBUG_PRINT(configuration.SPED.airDataRate, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.SPED.getAirDataRateDescription());
  DEBUG_PRINTLN(F(" "));
  DEBUG_PRINT(F("OptionSubPacketSett: "));  DEBUG_PRINT(configuration.OPTION.subPacketSetting, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.OPTION.getSubPacketSetting());
  DEBUG_PRINT(F("OptionTranPower    : "));  DEBUG_PRINT(configuration.OPTION.transmissionPower, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.OPTION.getTransmissionPowerDescription());
  DEBUG_PRINT(F("OptionRSSIAmbientNo: "));  DEBUG_PRINT(configuration.OPTION.RSSIAmbientNoise, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.OPTION.getRSSIAmbientNoiseEnable());
  DEBUG_PRINTLN(F(" "));
  DEBUG_PRINT(F("TransModeWORPeriod : "));  DEBUG_PRINT(configuration.TRANSMISSION_MODE.WORPeriod, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getWORPeriodByParamsDescription());
  DEBUG_PRINT(F("TransModeEnableLBT : "));  DEBUG_PRINT(configuration.TRANSMISSION_MODE.enableLBT, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getLBTEnableByteDescription());
  DEBUG_PRINT(F("TransModeEnableRSSI: "));  DEBUG_PRINT(configuration.TRANSMISSION_MODE.enableRSSI, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getRSSIEnableByteDescription());
  DEBUG_PRINT(F("TransModeFixedTrans: "));  DEBUG_PRINT(configuration.TRANSMISSION_MODE.fixedTransmission, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getFixedTransmissionDescription());


  DEBUG_PRINTLN("----------------------------------------");
}
void printModuleInformation(struct ModuleInformation moduleInformation) {
  Serial.println("----------------------------------------");
  DEBUG_PRINT(F("HEAD: "));  DEBUG_PRINT(moduleInformation.COMMAND, HEX);DEBUG_PRINT(" ");DEBUG_PRINT(moduleInformation.STARTING_ADDRESS, HEX);DEBUG_PRINT(" ");DEBUG_PRINTLN(moduleInformation.LENGHT, DEC);

  Serial.print(F("Model no.: "));  Serial.println(moduleInformation.model, HEX);
  Serial.print(F("Version  : "));  Serial.println(moduleInformation.version, HEX);
  Serial.print(F("Features : "));  Serial.println(moduleInformation.features, HEX);
  Serial.println("----------------------------------------");
}

 

When trying things out, it can be a bit annoying to adjust the settings via a separate sketch, even if this saves resources. Perhaps you would prefer to make the settings directly in the sketches that you also use for control? And perhaps you would like to have an overview of all the setting options? Then take a look at Appendix 1 for the E220 series.

Configuration Sketches for the E22 and E32 Series

In Appendix 2 and Appendix 3 you will find the counterparts to Appendix 1 for the E22 and E32 series respectively.  The sketches also provide a good overview of the differences between the three module series.

Settings in Detail

Address and Channel (configuration)

Each LoRa module has an address, which is made up of the higher byte ADDH and the lower byte ADDL. This allows you to set 65536 addresses. The address is assigned via configuration.ADDL = ... or configuration.ADDH = ....

The modules I tested cover the frequencies 850 – 930 MHz and 410 to 493 MHz. Fine adjustment is made via the channel. The formula for the frequency ν is

\nu_{Exxx-900}\; \text{[MHz]} = 850.125\ + \text{\it{CHANNEL}}
\nu_{Exxx-400}\; \text{[MHz]} = 410.125 + \text{\it{CHANNEL}}

For the Exxx-900 modules, use configuration.CHAN = 18 to set the frequency to 868.125 MHz. With the Exxx-400 models, you set 433.125 MHz with configuration.CHAN = 23.

There is a certain risk that you will transmit on a frequency that is not permitted in your country if you make the wrong selection. I’m just saying.

A check with a tinySA Spectrum Analyzer showed a good match with the target frequency within the accuracy of this device. Here is the result for channels 0, 18 and 80 of an E220-900T22D module:

Checking the transmission frequency E220-900T22D for channel 0, 18 and 80
Checking the transmission frequency for channel 0, 18 and 80 -> 850.13 / 868.20 / 930.12 MHz

The principle of channel setting is identical for all modules.

Transmission Rate and Options (configuration.SPED)

You can set the following parameters for UART communication and data transmission over the air (example E220):

  • uartBaudRate: Baud rate UART_BPS_xxx
    • xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
  • airDataRate: Data rate for radio transmission AIR_DATA_RATE_xxx
    • AIR_DATA_RATE_010_24: 2.4 kbit/s (default)
    • AIR_DATA_RATE_011_48: 4.8 kbit/s
    • AIR_DATA_RATE_100_96: 9.6 kbit/s
    • AIR_DATA_RATE_101_192: 19.2 kbit/s
    • AIR_DATA_RATE_110_384: 38.4 kbit/s
    • AIR_DATA_RATE_111_625: 62.5 kbit/s
  • uartParity: Parity MODE_xxx;
    • MODE_00_8N1: none (default)
    • MODE_01_8O1: odd
    • MODE_10_8E1: even

The options for the airDataRate differ for the E22 and 32 series. See Appendix 2 and 3.

Regarding the data rate via radio: the lower the data rate, the higher the maximum range.

Further Transmission Options (configuration.OPTION)

Further options (example E220):

  • subPacketSetting: Maximum data packet size, i.e. the maximum length of your message that you can send continuously in one piece.
    • SPS_200_00: 200 bytes
    • SPS_128_01: 128 bytes
    • SPS_064_10: 64 bytes
    • PLC_032_11: 32 bytes
  • RSSIAmbientNoise: RSSI (Received Signal Strength Indicator) Ambient Noise enable
    • RSSI_AMBIENT_NOISE_DISABLED: Function is switched off
    • RSSI_AMBIENT_NOISE_ENABLED: Function is switched on
  • transmissionPower: Transmission power POWER_xx with xx = power in dbm. The setting options depend on the model you are using.
    • xx for E220….22D: 22, 17, 13, 10
    • xx for E220….30D: 30, 27, 24, 21

The subPacketSetting and transmissionPower settings differ for the E22 and E32 series (see Appendix 2 and 3). RSSIAmbientNoise is not available on the E32 module.

Mode Settings (configuration.TRANSMISSION_MODE)

I will come back to some of these settings later. Here are the options for the E220 series:

  • enableRSSI: Signal strength information (Received Signal Strength Indication)
    • RSSI_DISABLED: disabled
    • RSSI_ENABLED: enabled
  • fixedTransmission: Transmission mode
    • FT_FIXED_TRANSMISSION: Transmission to a specific address / channel.
    • FT_TRANSPARENT_TRANSMISSION: Transmission to all modules with identical address and channel.
  • enableLBT: LBT (Listen Before Talk) is a function that causes the module to wait up to two seconds for a favorable moment (with low interference) before transmitting.
    • LBT_DISABLED: Function disabled
    • LBT_ENABLED: Function enabled
  • WORPeriod: WOR (Wake On Radio) period WOR_xxx_yyy with xxx = wake-up period in milliseconds
    • xxx_yyy = 500_000, 1000_001, 1500_010, 2000_011, 2500_100, 3000_101, 3500_110, 4000_111

The WORPeriod parameter needs a little more explanation. This is the period after which the receiver wakes up to check whether a message is coming. Of course, the sender does not know when the receiver is awake and therefore sends a “preamble” with the length of the wake-up period. The WOR receiver then remains awake until the actual message is received. For this reason, the WOR Receiver and the WOR Transmitter must have the same WORPeriod set.

The WORPeriod is called wirelessWakeUpTime when using the E32 series. The selectable periods are also different. The enableRSSI and enableLBT settings are not available on the E32 modules.

Encryption (configuration.CRYPT)

All modules encrypt the messages. However, only the E22 and E220 series modules allow individual encryption. To do this, you define values for the two bytes CRYPT_H and CRYPT_L. The setting must, of course, be identical for the transmitter and receiver. A fixed encryption setting is used on the E32.

Response Container and Response Status

Before we finally come to the pratical part, I need to explain two structures defined in the library, namely ResponseContainer and ResponseStatus. They contain information about the incoming and outgoing messages.

The ResponseContainer contains the actual message data, the signal strength value rssi, and the ResponseStatus status.

struct ResponseContainer {
  String data;
  byte rssi; // only E22 and E220 series!
  ResponseStatus status;
};

The ResponseStatus is also a structure. It contains the error code code and the function getResponseDescription(), which returns a string that translates the code comprehensibly. For example, error code 1 (E220_SUCCESS) means “Success”.

struct ResponseStatus {
  Status code;
  String getResponseDescription() {
    return getResponseDescriptionByParams(this->code);
  }
};

Transparent Transmission Mode

But now to the first sketch. We allow several (at least two) modules to communicate with each other in “Transparent Transmission Mode”. In this mode, you can reach all LoRa modules that have the same address and channel set:

Scheme for transparent transmission mode
Scheme for transparent transmission mode

To try out the transparent mode, you should not normally need to make any changes to the settings. However, if you have problems, set the modules to the correct state with setConfiguration_modified.ino.

Then upload the lora_transparent.ino sketch to the microcontroller boards. You may need to adjust lines 3 and 4 for your boards. Take a look at the original example sketches in the library – there are many ready-made settings for different boards. 

In most examples, we need a separate serial monitor for each board. With the old Arduino IDE 1.x, this was easy by simply calling up the IDE several times, i.e. creating several instances and selecting a separate serial port for each instance. The IDE 2.x does not seem to allow multiple instances. You can help yourself by saving the sketch under different names, opening the versions, and then setting a separate port for each sketch. 

#include "LoRa_E220.h"
 
SoftwareSerial mySerial(4,5);
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, I'm going to send message!");
 
  // Startup all pins and UARTD
  e220ttl.begin();
 
  // Send message
  ResponseStatus rs = e220ttl.sendMessage("Hello, world?");
  // Check if there is some problem of successfully send
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
    // If something available
  if (e220ttl.available()>1) {
      // read the String message
    ResponseContainer rc = e220ttl.receiveMessage();
    // Is something goes wrong print error
    if (rc.status.code!=1){
        Serial.println(rc.status.getResponseDescription());
    }else{
        // Print the data received
        Serial.println(rc.data);
    }
  }
  if (Serial.available()) {
      String input = Serial.readString();
      e220ttl.sendMessage(input);
  }
}

 

Apart from the library to be integrated and the object creation (line 4), the sketch works in the same way with the E22 and E32 boards.

Explanations for lora_transparent.ino

First create the object e220ttl and initialize your module with e220ttl.begin().

You send messages using sendMessage(). As you can see, you do not enter an address or channel in transparent mode. The module automatically sends to the modules with the same settings. The return value of sendMessage() is a structure of the type ResponseStatus, which I explained above. You can get a readable translation of the status code with getResponseDescription(). Ideally, this is “Success”. However, “Success” only means that the message was sent successfully and not that it was received.

You can check whether a message has been received on the receiver side with e220ttl.available(). You are reading the message using receiveMessage(). More precisely, receiveMessage() returns a structure of the type ResponseContainer, which contains the actual message in the element data.

The ResponseContainer “rc” in turn contains the ResponseStatus “rc.status”. You check with

if (rc.status.code != 1) {....

whether an error has occurred. Perhaps the variant

if (rc.status.getResponseDescription() = "Success") {...

would be a little easier to understand, but I wanted to stay close to the original library sketches.

If everything is OK, the message is displayed, if not, the corresponding error message appears.

Use if (Serial.available(){... to check whether an input has been made via the serial monitor. If this is the case, the entry is read and sent.

Output lora_transparent.ino

I used three modules. Below, you can see the output of the first activated module. The two “Hello, world?” messages were sent via the setup of the other modules. I sent the two other messages “manually” from the other modules by entering them in the serial monitor.

Output lora_transparent. ino
Output lora_transparent. ino

Sending and receiving Structures

In most cases, you probably do not want to transmit character strings, but data, such as that of a weather station. In the following example, we send the humidity (integer), the temperature (float) and the rain status (bool) every five seconds. To send this data as a “package”, we define the structure weatherData.

For this example, we will remain in transparent mode. The two sketches for the transmitter and receiver only need to be changed for the E22 and E32 modules regarding the library to be integrated and the object creation.

Transmitter

The sketch for the transmitter should be largely self-explanatory:

#include "LoRa_E220.h"
 
SoftwareSerial mySerial(4,5);
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1

struct weatherData {
  int humidity;
  float temperature;
  bool rain;
};

weatherData currentWeather = {50, 20.0, false};
 
void setup() {
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, I'm going to send message!");
 
  // Startup all pins and UARTD
  e220ttl.begin();
 
  // Send message
  ResponseStatus rs = e220ttl.sendMessage("Hello, world?");
  // Check if there is some problem of successfully send
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
  static unsigned long lastSend = millis(); 
  if(millis() - lastSend > 5000){
      currentWeather.humidity = 30;
      currentWeather.temperature = 23.7;
      currentWeather.rain = false; 
      e220ttl.sendMessage(&currentWeather, sizeof(currentWeather));
      lastSend = millis();
  }
}

 

Regarding the sending process, the only difference to the previous sketch is that we do not pass sendMessage() a string, but a structure (as reference using “&”) and the size of the structure.

Receiver

To be able to process the structure to be received, we use the structureur ResponseStructContainer instead of ResponseContainer, which is defined as follows:

struct ResponseStructContainer {
  void *data;
  byte rssi;
  ResponseStatus status;
  void close() {
    free(this->data);
  }
};

The data type void* is an untyped pointer that many readers may not be familiar with. In contrast to the usual pointers such as int*, its meaning, i.e. the data type, must first be assigned to it.

Here is the sketch:

#include "LoRa_E220.h"
 
SoftwareSerial mySerial(4,5);
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1

struct weatherData {
  int humidity;
  float temperature;
  bool rain;
};
 
void setup() {
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, waiting for weather data...");
  // Startup all pins and UARTD
  e220ttl.begin();
}
 
void loop() {
  if (e220ttl.available()>1) {
      // read the String message
    ResponseStructContainer rsc = e220ttl.receiveMessage(sizeof(weatherData));
    weatherData currentWeather = *(weatherData*) rsc.data;
    Serial.print("Humidity     [%]: ");
    Serial.println(currentWeather.humidity);
    Serial.print("Temperature [°C]: ");
    Serial.println(currentWeather.temperature);
    Serial.print("Rain            : ");
    if(currentWeather.rain){
      Serial.println("yes");
    }
    else{
      Serial.println("no");
    }
    Serial.println();
  }
}

 

The line:

ResponseStructContainer rsc = e220ttl.receiveMessage(sizeof(weatherData));

reads the received message and saves it in ResponseStructContainer “rsc”. Please note that we must pass the size of the expected data packet to receiveMessage().

The actual data is stored as data type void* in rsc.data. To be able to do something with this, we cast the data type void* into a pointer with (weatherData*), which points to the structure of type weatherData. To save the data in the structure currentWeather, we have to dereference the pointer with another *. So:

weatherData currentWeather = *(weatherData*) rsc.data;

The output is still missing. It’s a bit boring, of course, because we always send the same data. But this is only about the principle.

Output lora_struct_receiver.ino

Fixed Transmission Mode

You set the fixed transmission mode by changing the line

configuration.TRANSMISSION_MODE.fixedTransmission = FT_TRANSPARENT_TRANSMISSION;

to:

configuration.TRANSMISSION_MODE.fixedTransmission = FT_FIXED_TRANSMISSION;

There are two options for sending messages in fixed transmission mode:

  1. Exclusive transmission to the modules with a specific address on a specific channel:
    • Function: sendFixedMessage(ADDH, ADDL, channel, message);
    • The transmitting module itself may have a different address and a different channel set.
  2. Sending to all modules on a specific channel, regardless of their address (“broadcasting”):
    • Function: sendBroadcastFixedMessage(channel, message)
      • which corresponds to: sendFixedMessage(0xFF, 0xFF, channel, message), as 0xFFFF is the broadcasting address.
    • The transmitting module may also have a different channel set here.

I have two diagrams to illustrate this. Here first for sending to a specific address:

LoRa E220: Schematic for Fixed Transmission Mode
Scheme for fixed transmission mode

And here is the diagram for broadcasting in fixed transmission mode:

LoRa E220: Scheme for broadcast message in fixed transmission mode
Scheme for broadcasting in fixed transmission mode

Example Sketch Fixed Transmission Mode

To test the fixed transmission mode, I used three modules with the following settings:

  1. ADDH = 0x00, ADDL = 0x01, Channel = 18
  2. ADDH = 0x00, ADDL = 0x02, Channel = 18
  3. ADDH = 0x00, ADDL = 0x03, Channel = 18

I have uploaded the following sketch to the three controlling Arduino boards, whereby I have only adapted the “greeting” in line 16 and the switch construction from line 38.

The sketch allows messages entered via the serial monitor to be sent to specific addresses (input: “x,message” with x = 1,2,3) or to all modules via broadcasting (input: “18,message”). 

The sketch only needs to be adapted for use on E22 and E32 modules regarding the library and object creation. 

#include "LoRa_E220.h"

SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, I'm going to send message!");
 
  // Startup all pins and UART
  e220ttl.begin();
 
  // Send message
  ResponseStatus rs = e220ttl.sendBroadcastFixedMessage(18,"Hi to all receivers! This is no. 1"); // adjust
  // Check If there is some problem of successfully send
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
    // If something available
  if (e220ttl.available()>1) {
      // read the String message
    ResponseContainer rc = e220ttl.receiveMessage();
    // Is something goes wrong print error
    if (rc.status.code!=1){
        Serial.println(rc.status.getResponseDescription());
    }else{
        // Print the data received
        Serial.println(rc.data);
    }
  }
  if (Serial.available()) {
    int addr = Serial.parseInt();
    String input = Serial.readString();
    input = input.substring(input.indexOf(",")+1);
    switch(addr){
      case 2:
        e220ttl.sendFixedMessage(0,2,18,input);
        break;
      case 3:
        e220ttl.sendFixedMessage(0,3,18,input);
        break;
      case 18:
        e220ttl.sendBroadcastFixedMessage(18,input);
        break;
      default:
        e220ttl.sendBroadcastFixedMessage(18,input);
    }
  }
}

 

Here is the output of module 3 (ADDL = 3). To receive the greetings (“Hi to all receivers! …”) from the other modules, it had to be initialized before them.

Output

Of course, structures can also be sent in fixed transmission mode. However, I try to keep my examples simple and limit them to the relevant topic.

RSSI Received Signal Strength Indicator

As the RSSI function is not available on the E32 modules, this section only applies to the E22 and E220 modules.

The RSSI is a dimensionless number of the byte datatype that tells you how strong the received signal was. The number cannot be directly translated into a signal strength in dBm. To read out the RSSI, simply replace receiveMessage() with ReceiveMessageRSSI() on the receiver side, for example:

ResponseContainer rc = e220ttl.receiveMessageRSSI();

Here is an example sketch:

#include "LoRa_E220.h"
 
SoftwareSerial mySerial(4,5);
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, I'm going to send message!");
 
  // Startup all pins and UART
  e220ttl.begin();
 
  // Send message
  ResponseStatus rs = e220ttl.sendFixedMessage(0,2,18,"Hello, world?");
  // Check If there is some problem of successfully send
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
    // If something available
  if (e220ttl.available()>1) {
      // read the String message
    ResponseContainer rc = e220ttl.receiveMessageRSSI();
    // Is something goes wrong print error
    if (rc.status.code!=1){
        Serial.println(rc.status.getResponseDescription());
    }else{
        // Print the data received
        String output = rc.data;
        byte rssiVal =  rc.rssi;
        Serial.println(rc.data);
        Serial.print("RSSI: ");
        Serial.println(rc.rssi);
    }
  }
  if (Serial.available()) {
      String input = Serial.readString();
      e220ttl.sendFixedMessage(0,2,18,input);
  }
}

For the following receiver output, I placed two modules on my desk and sent two messages. For the first message, I used antennas for both modules. Before sending the second message, I disconnected the antenna from the receiving module. As you can see, the received signal strength has dropped accordingly. As you can also see, the RSSI is not a measure of the actual signal strength, as this is the same in both cases.

Output lora_fixed_rssi.ino
Output lora_fixed_rssi.ino

Using Wake On Radio (WOR)

The power consumption of the LoRa module series E220, E22 and E32 can be significantly reduced if they are operated in WOR mode (“Wake On Radio”). As mentioned above, the transmitter and receiver must have the same WOR period set. 

WOR Transmitter

The only additional setting you need to make on the transmitter side is the WOR transmitter mode:

e220ttl.setMode(MODE_1_WOR_TRANSMITTER);

You do this directly in your application sketch, i.e. not via the configuration sketch. Despite its role as a WOR transmitter, the module can still receive messages.

Here is the sketch for the WOR transmitter:

#include "LoRa_E220.h"

SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1

void setup() {
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, I'm going to send a WOR message!");
 
  e220ttl.begin();
  e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
 
  // Send message
  ResponseStatus rs = e220ttl.sendFixedMessage(0,2,18,"Hello, world? WOR!");
  // Check If there is some problem of successfully send
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
    // If something available
  if (e220ttl.available()>1) {
      // read the String message
    ResponseContainer rc = e220ttl.receiveMessage();
    // Is something goes wrong print error
    if (rc.status.code!=1){
       Serial.println(rc.status.getResponseDescription());
    }else{
        // Print the data received
        Serial.println(rc.data);
    }
  }
  if (Serial.available()) {
      String input = Serial.readString();
      e220ttl.sendFixedMessage(0,2,18,input);
  }
}

 

The output is still missing. As you can see below, the transmitter receives a confirmation of receipt. This is not a feature of WOR mode, but a feature of the receiver sketch, to which we will come soon.

Output lora_wor_transmitter.ino
Output lora_wor_transmitter.ino

Instead of setMode(MODE_1_WOR_TRANSMITTER) you use setMode(MODE_1_WAKE_UP) for the E32 modules. For the E22 modules, set mode MODE_1_WOR in the sketch and define the role as WOR transmitter via the configuration sketch. 

WOR Receiver

You can set the WOR receiver mode as follows:

e220ttl.setMode(MODE_2_WOR_RECEIVER);

To be able to send back the confirmation of receipt, you must switch the receiver to normal mode using e220ttl.setMode(MODE_0_NORMAL);.

You do not necessarily need the interrupt inserted here, which is triggered by the falling edge of the AUX pin. It is only used to announce the output of the incoming message via the TX pin. Here is the diagram of the behavior of the AUX pin and TX pin:

AUX pin vs. TX pin when outputting a message via TX
AUX pin vs. TX pin when outputting a message via TX
#include "LoRa_E220.h"
#define AUX_PIN 3
volatile bool interruptExecuted = false;
 
SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, AUX_PIN, 7, 6); // AUX M0 M1


void wakeUp() {
  interruptExecuted = true;
  //detachInterrupt(digitalPinToInterrupt(AUX_PIN));
}

void setup() {
  Serial.begin(9600);
  delay(500);
 
  e220ttl.begin();
  e220ttl.setMode(MODE_2_WOR_RECEIVER);

  Serial.println("Start sleep!");
  delay(100);
  attachInterrupt(digitalPinToInterrupt(AUX_PIN), wakeUp, FALLING);
}
 
void loop() {
  //  If something available
  if (e220ttl.available()>1) {
    Serial.println("Message arrived");
      // read the String message
    ResponseContainer rc = e220ttl.receiveMessage();

    String message = rc.data;
    //Serial.println(rc.status.getResponseDescription());
    Serial.println(message);

    e220ttl.setMode(MODE_0_NORMAL); // change to normal mode
    delay(1000);

    e220ttl.sendFixedMessage(0, 1, 18, "We have received the message!");
    e220ttl.setMode(MODE_2_WOR_RECEIVER); // change back to WOR receiver mode
    interruptExecuted = false;
  }
 
  if(interruptExecuted) {
    Serial.println("WakeUp Callback, AUX pin go LOW and start receive message!");
    Serial.flush();
    //attachInterrupt(digitalPinToInterrupt(AUX_PIN), wakeUp, FALLING);
    interruptExecuted = false;
  }
}

 

And here is the output:

Output lora_wor_receiver.ino
Output lora_wor_receiver.ino

Instead of setMode(MODE_2_WOR_RECEIVER) you use setMode(MODE_2_POWER_SAVING) for the E32 modules. For the E22 modules, set the mode MODE_1_WOR in the sketch and define the role as WOR receiver before as a permanent setting via setConfiguration.ino. 

Waking up the MCU with WOR

Since the AUX pin goes LOW after / while receiving the message and before outputting it to the microcontroller, this signal can be used to wake up not only the LoRa module but also a sleeping microcontroller via an external interrupt.

Unfortunately, the lead time between the falling edge of the AUX pin and the start of transmission via the TX pin cannot be set (at least I have not found a way to do this). Two to three milliseconds may be too little time, for example, to bring an ESP32 back from deep sleep. On the Arduino Nano, I noticed that at least the first character of the transmitted message was missing. A pragmatic solution, at least for my Arduino Nano configuration, would be to prefix the message with a few dummy characters that can be sacrificed.

I have chosen an alternative solution in my example, namely we first send a “wake-up call”. Before the actual message goes out, we send the transmitter a confirmation that the receiver is awake.

Here is the transmitter sketch:

Transmitter Side

#include "LoRa_E220.h"

SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
  Serial.println("Hello, starting now. Type in a message.");

  e220ttl.begin();
  e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
}
 
void loop() {
  if (Serial.available()) {
    String input = Serial.readString();
    e220ttl.sendFixedMessage(0,2,18,"......wake up!!"); // wake up call
    e220ttl.setMode(MODE_0_NORMAL); // change to normal mode
    delay(100); // give time for the receiver to wake up
    
    while(e220ttl.available()<= 1); // wait for confirmation
    ResponseContainer rc = e220ttl.receiveMessage(); // receive message
    rc.status.getResponseDescription(); 
    Serial.println(rc.data);
    
    e220ttl.sendFixedMessage(0,2,18,input); // send the actual message
    e220ttl.setMode(MODE_1_WOR_TRANSMITTER); // change back to WOR transmitter mode
  }
}

 

And here is the unsurprising output:

Here too – apart from the general adjustments – you must make the setting setMode(MODE_1_WAKE_UP) instead of setMode(MODE_1_WOR_TRANSMITTER) for the E32 modules. For the E22 modules, set the mode MODE_1_WOR in the sketch and define the role as WOR transmitter as a permanent setting.  

Receiver Side

The sleep modes of microcontrollers are hardware-specific. I have written an article about the sleep modes of AVR microcontrollers here. If you are not using an AVR-based microcontroller board, you will have to adapt the sketch accordingly.

#include "LoRa_E220.h"
#include <avr/sleep.h>

#define AUX_PIN 3

SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, AUX_PIN, 7, 6); // AUX M0 M1

void wakeUp() {
  delay(0); // add code if you want
}


void setup() {
  Serial.begin(9600);
  delay(500);

  // Startup all pins and UART
  e220ttl.begin();

  e220ttl.setMode(MODE_2_WOR_RECEIVER);

  Serial.println("Start sleep!");
  delay(100);
  attachInterrupt(digitalPinToInterrupt(AUX_PIN), wakeUp, FALLING);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // define sleep mode
}
 
 
void loop() {
  sleep_mode(); // set MCU (Arduino Nano) to sleep
  delay(10);
  if (e220ttl.available()>1) {  // wait for wake up
    ResponseContainer rc = e220ttl.receiveMessage();
    String message = rc.data;
    Serial.println(message);

    e220ttl.setMode(MODE_0_NORMAL);
    delay(200);
    e220ttl.sendFixedMessage(0, 1, 18, "Receiver woke up!");
    delay(100);
    
    while(e220ttl.available()<= 1); // wait for second message
    rc = e220ttl.receiveMessage();
    message = rc.data;
    Serial.println(message);
    Serial.flush();
    e220ttl.setMode(MODE_2_WOR_RECEIVER);
  }
}

 

Here is the output:

Output lora_wor_wake_up_mcu_receiver.ino
Output lora_wor_wake_up_mcu_receiver.ino

If you change the WORPeriod, you must change the delays in the sketches so that the interaction works.

For E32 modules, among other things, setMode(MODE_2_WOR_RECEIVER) must be replaced by setMode(MODE_2_POWER_SAVING). For the E22 modules, set the mode MODE_1_WOR in the sketch and define the role as WOR receiver as a permanent setting.

Range Test

I carried out a range test with the E220-900T22D module. According to the data sheet, the range is up to 5 km, but only if there is a clear line of sight between the modules, which is rather rare at this distance.

I selected the highest transmission power of 22 dBm and the lowest data rate because this should provide the greatest range. The antenna used was an 868 MHz antenna. The Arduino was supplied with power via a 9 V lithium battery. Measures for voltage stabilization such as additional capacitors were not used.

Here is the receiver unit:

LoRa E220 range test setup (receiver)
LoRa E220 range test setup (receiver)

The transmitter has sent a message every 5 seconds:

#include "LoRa_E220.h"

SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, I'm going to send message!");
 
  // Startup all pins and UART
  e220ttl.begin();
 
  // Send message
  ResponseStatus rs = e220ttl.sendFixedMessage(0,2,18,"Hi to receiver!"); // adjust
  // Check If there is some problem of successfully send
  Serial.println(rs.getResponseDescription());
  delay(2000);
}
 
void loop() {
  // If something available
  static unsigned long lastSend = 0;
  if (millis() - lastSend > 5000) {
    e220ttl.sendFixedMessage(0,2,18, "Hi Receiver, did you get this message?");
    lastSend = millis();
  }
}

 

The receiver has received the message, checked the content and, if correct, briefly illuminated an LED on pin 8 five times.

#include "LoRa_E220.h"
#define LED_PIN 8

SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  delay(500);
 
  Serial.println("Hi, waiting for messages!");
 
  // Startup all pins and UART
  e220ttl.begin();
  delay(2000);
}
 
void loop() {
  // If something available
  if (e220ttl.available()>1) {
    ResponseContainer rc = e220ttl.receiveMessage();
    //Serial.println(rc.data);
    if(rc.data = "Hi Receiver, did you get this message?"){
      for(int i=0; i<5; i++){
        digitalWrite(LED_PIN, HIGH);
        delay(100);
        digitalWrite(LED_PIN, LOW);
        delay(100);
      }
    }
  }
}

 

The transmitter was placed on my desk at home. I went out into the fields with the receiver unit and checked up to what distance I could receive messages. The radio signal first had to penetrate my own house wall, then a narrow strip of woodland and three or four houses before it crossed the open field. The terrain was flat. This gave me a range of 1.34 km.

Result of the range test
Result of the range test (measured with Google Maps).

Appendices – Transceiver Sketches with Settings for E220, E22, E32

Appendix 1 – E220 Transceiver Sketch with Settings

Sketch with embedded configuration for E220 modules. You can copy the function setConfiguration() and use it in other sketches. The sketch was tested with two E220-900T22D modules.

// #define FREQUENCY_433 // default value without set 
// #define FREQUENCY_170
// #define FREQUENCY_470
#define FREQUENCY_868
// #define FREQUENCY_915
// #define E220_22 // default value without set 
// #define E220_30 // uncomment in case you use an E220...T30D or E220...T30S
#include "LoRa_E220.h"
 
SoftwareSerial mySerial(4,5);
LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
  
  Serial.println("Beginning configuration...");
  e220ttl.begin();
  setConfiguration();
  Serial.println("Hi, I'm going to send message!");
 
  ResponseStatus rs = e220ttl.sendBroadcastFixedMessage(18, "Hello, world?");
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
    // If something available
  if (e220ttl.available()>1) {
      // read the String message
    ResponseContainer rc = e220ttl.receiveMessage();
    // Is something goes wrong print error
    if (rc.status.code!=1){
        Serial.println(rc.status.getResponseDescription());
    }else{
        // Print the data received
        Serial.println(rc.data);
    }
  }
  if (Serial.available()) {
      String input = Serial.readString();
      e220ttl.sendFixedMessage(0, 2, 18, input);
  }
}

void setConfiguration(){
  ResponseStructContainer c;
  c = e220ttl.getConfiguration();
  // It's important get configuration pointer before all other operation
  Configuration configuration = *(Configuration*) c.data;
  Serial.println(c.status.getResponseDescription());
  Serial.println(c.status.code);

  configuration.ADDL = 0x03;  // Low byte of address
  configuration.ADDH = 0x00; // High byte of address

  configuration.CHAN = 18; // Communication channel --> 868 MHz

  /*
  UART_BPS_xxx with xxx = Baudrate
  xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
  */
  configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate

  /*
  AIR_DATA_RATE_000_24     2.4 kb/s
  AIR_DATA_RATE_001_24     2.4 kb/s
  AIR_DATA_RATE_010_24     2.4 kb/s
  AIR_DATA_RATE_011_48     4.8 kb/s
  AIR_DATA_RATE_100_96     9.6 kb/s
  AIR_DATA_RATE_101_192   19.2 kb/s
  AIR_DATA_RATE_110_384   38.4 kb/s
  AIR_DATA_RATE_111_625   62.5 kb/s 
  */
  configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate
  
  /* 
  MODE_00_8N1  none
  MODE_01_8O1  odd
  MODE_10_8E1  even
  MODE_11_8N1  none
  */
  configuration.SPED.uartParity = MODE_00_8N1; // Parity bit

  /*
  SPS_200_00  200
  SPS_128_01  128
  SPS_064_10   64
  SPS_032_11   32
  */
  configuration.OPTION.subPacketSetting = SPS_200_00; // Packet size

  /*
  RSSI_AMBIENT_NOISE_DISABLED
  RSSI_AMBIENT_NOISE_ENABLED
  */
  configuration.OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; // Need to send special command

  /*
  POWER_xxx with xxx = power in dBm
  E220...T22D/S: xxx = 22, 17, 13, 10
  E220...T30D/S: xxx = 30, 27, 24, 21
  */
  configuration.OPTION.transmissionPower = POWER_22; // Device power

  /*
  RSSI_DISABLED
  RSSI_DISABLED
  */
  configuration.TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; // Enable RSSI info

  /*
  FT_FIXED_TRANSMISSION
  FT_TRANSPARENT_RANSMISSION
  */
  configuration.TRANSMISSION_MODE.fixedTransmission = FT_FIXED_TRANSMISSION; // Transmission mode

  /*
  LBT_DISABLED
  LBT_ENABLED
  */
  configuration.TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // Check interference

  /*
  WOR_xxx_yyy with xxx = WOR Period
  xxx_yyy = 500_000, 1000_001, 1500_010, 2000_011, 2500_100, 3000_101, 3500_110, 4000_111
  */
  configuration.TRANSMISSION_MODE.WORPeriod = WOR_2000_011; // WOR timing

  configuration.CRYPT.CRYPT_H = 0x00;  // encryption high byte, default: 0x00
  configuration.CRYPT.CRYPT_L = 0x00;  // encryption low byte, default: 0x00

  // Set configuration changed and set to hold the configuration
  ResponseStatus rs = e220ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); // WRITE_CFG_PWR_DWN_LOSE to not permanently save

  Serial.println(rs.getResponseDescription());
  Serial.println(rs.code);
  c.close();
}

 

Appendix 2 – E22 Transceiver Sketch with Settings

Sketch with embedded configuration for E22 modules. You can copy the function setConfiguration() and use it in other sketches. The sketch was tested with two E22-400T22D modules.

// #define FREQUENCY_433 // default value without set 
// #define FREQUENCY_170
// #define FREQUENCY_470
// #define FREQUENCY_868
// #define FREQUENCY_915
// #define E22_22 // default value without set 
// #define E22_30 // uncomment in case you use an E22...T30D or E22...T30S
#include "LoRa_E22.h"
 
SoftwareSerial mySerial(4,5);
LoRa_E22 e22ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
  
  Serial.println("Beginning configuration...");
  e22ttl.begin();
  setConfiguration();
  Serial.println("Hi, I'm going to send message!");
 
  ResponseStatus rs = e22ttl.sendBroadcastFixedMessage(23, "Hello, world?");
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
    // If something available
  if (e22ttl.available()>1) {
      // read the String message
    ResponseContainer rc = e22ttl.receiveMessage();
    // Is something goes wrong print error
    if (rc.status.code!=1){
        Serial.println(rc.status.getResponseDescription());
    }else{
        // Print the data received
        Serial.println(rc.data);
    }
  }
  if (Serial.available()) {
      String input = Serial.readString();
      e22ttl.sendFixedMessage(0, 2, 23, input);
  }
}

void setConfiguration(){
  ResponseStructContainer c;
  c = e22ttl.getConfiguration();
  // It's important get configuration pointer before all other operation
  Configuration configuration = *(Configuration*) c.data;
  Serial.println(c.status.getResponseDescription());
  Serial.println(c.status.code);

  configuration.ADDL = 0x03;  // Low byte of address
  configuration.ADDH = 0x00; // High byte of address
  configuration.NETID = 0x00; // used for repeater function

  configuration.CHAN = 23; // Communication channel --> 433 MHz

  /*
  UART_BPS_xxx with xxx = Baudrate
  xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
  */
  configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate

  /*
  AIR_DATA_RATE_000_03     0.3 kb/s
  AIR_DATA_RATE_001_12     1.2 kb/s
  AIR_DATA_RATE_010_24     2.4 kb/s
  AIR_DATA_RATE_011_48     4.8 kb/s
  AIR_DATA_RATE_100_96     9.6 kb/s
  AIR_DATA_RATE_101_192   19.2 kb/s
  AIR_DATA_RATE_110_384   38.4 kb/s
  AIR_DATA_RATE_111_625   62.5 kb/s 
  */
  configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate
  
  /* 
  MODE_00_8N1  none
  MODE_01_8O1  odd
  MODE_10_8E1  even
  MODE_11_8N1  none
  */
  configuration.SPED.uartParity = MODE_00_8N1; // Parity bit

  /*
  SPS_240_00  200
  SPS_128_01  128
  SPS_064_10   64
  SPS_032_11   32
  */
  configuration.OPTION.subPacketSetting = SPS_240_00; // Packet size

  /*
  RSSI_AMBIENT_NOISE_DISABLED
  RSSI_AMBIENT_NOISE_ENABLED
  */
  configuration.OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; // Need to send special command

  /*
  POWER_xxx with xxx = power in dBm
  E22...T22D/S: xxx = 22, 17, 13, 10
  E22...T30D/S: xxx = 30, 27, 24, 21
  */
  configuration.OPTION.transmissionPower = POWER_22; // Device power

  /*
  RSSI_DISABLED
  RSSI_DISABLED
  */
  configuration.TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; // Enable RSSI info

  /*
  FT_FIXED_TRANSMISSION
  FT_TRANSPARENT_RANSMISSION
  */
  configuration.TRANSMISSION_MODE.fixedTransmission = FT_FIXED_TRANSMISSION; // Transmission mode

  /*  In the repeater mode, ADDH/ADDL is no longer used as the module address, it is used as a NETID 
  to pair and forwarding. If the reperater receive the data from a network, then it will forward the 
  data to the other network. The network ID of the repeater itself is invalid in this case.
  The repeater module cannot transmit and receive data, and cannot perform low-power operation.
  REPEATER_ENABLED 
  REPEATER_DISABLED
  */
  configuration.TRANSMISSION_MODE.enableRepeater = REPEATER_DISABLED;

  /*
  LBT_DISABLED
  LBT_ENABLED
  */
  configuration.TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // Check interference

  /*
  WOR_TRANSMITTER
  WOR_RECEIVER
  */
  configuration.TRANSMISSION_MODE.WORTransceiverControl = WOR_RECEIVER;

  /*
  WOR_xxx_yyy with xxx = WOR Period in ms
  xxx_yyy = 500_000, 1000_001, 1500_010, 2000_011, 2500_100, 3000_101, 3500_110, 4000_111
  */
  configuration.TRANSMISSION_MODE.WORPeriod = WOR_2000_011; // WOR timing

  configuration.CRYPT.CRYPT_H = 0x00;  // encryption high byte, default: 0x00
  configuration.CRYPT.CRYPT_L = 0x00;  // encryption low byte, default: 0x00

  // Set configuration changed and set to not hold the configuration
  ResponseStatus rs = e22ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); // WRITE_CFG_PWR_DWN_LOSE to not permanently save

  Serial.println(rs.getResponseDescription());
  Serial.println(rs.code);
  c.close();
}

 

Appendix 3 – E32 Transceiver Sketch with Settings

Sketch with embedded configuration for E32 modules. You can copy the function setConfiguration() and use it in other sketches.  The sketch was tested with two E220-900T22D modules.

// #define FREQUENCY_433 // default value without set 
// #define FREQUENCY_170
// #define FREQUENCY_470
// #define FREQUENCY_868
// #define FREQUENCY_915/* Choose your module */
// #define E32_TTL_100 
// #define E32_TTL_500 
#define E32_TTL_1W // E32-TTL-1W, E32-433T30S/D, E32-868T30S/D, E32-915T30S/D
// #define E32_TTL_2W
#include "LoRa_E32.h"
 
SoftwareSerial mySerial(4,5);
LoRa_E32 e32ttl(&mySerial, 3, 7, 6); // AUX M0 M1
 
void setup() {
  Serial.begin(9600);
  delay(500);
  
  Serial.println("Beginning configuration...");
  e32ttl.begin();
  setConfiguration();
  Serial.println("Hi, I'm going to send message!");
 
  ResponseStatus rs = e32ttl.sendBroadcastFixedMessage(23, "Hello, world?");
  Serial.println(rs.getResponseDescription());
}
 
void loop() {
    // If something available
  if (e32ttl.available()>1) {
      // read the String message
    ResponseContainer rc = e32ttl.receiveMessage();
    // Is something goes wrong print error
    if (rc.status.code!=1){
        Serial.println(rc.status.getResponseDescription());
    }else{
        // Print the data received
        Serial.println(rc.data);
    }
  }
  if (Serial.available()) {
      String input = Serial.readString();
      e32ttl.sendFixedMessage(0, 2, 23, input);
  }
}

void setConfiguration(){
  ResponseStructContainer c;
  c = e32ttl.getConfiguration();
  // It's important get configuration pointer before all other operation
  Configuration configuration = *(Configuration*) c.data;
  Serial.println(c.status.getResponseDescription());
  Serial.println(c.status.code);

  configuration.ADDL = 0x03;  // Low byte of address
  configuration.ADDH = 0x00; // High byte of address

  configuration.CHAN = 23; // Communication channel --> 433 MHz

  /* After turning off FEC, the actual data transmission rate increases 
  while anti-interference ability decreases. Also, the transmission distance is relatively short, and both communication parties must keep on the same pages about turn-on or turn-off FEC.
  FEC_0_OFF
  FEC_1_ON
  */
  configuration.OPTION.fec = FEC_1_ON;

  /*
  FT_FIXED_TRANSMISSION
  FT_TRANSPARENT_RANSMISSION
  */
  configuration.OPTION.fixedTransmission = FT_FIXED_TRANSMISSION; // Transmission mode
  
  /* Using internal pull-up resistors may make external redundant 
  IO_D_MODE_OPEN_COLLECTOR
  IO_D_MODE_PUSH_PULLS_PULL_UPS
  */
  configuration.OPTION.ioDriveMode = IO_D_MODE_PUSH_PULLS_PULL_UPS;

  /* for E32_TTL_1W
  POWER_xxx with xxx = power in dBm
  E32_TTL_100: xxx = 20, 17, 14, 10
  E32_TTL_500: xxx = 27, 24, 21, 18
  E32_TTL_1W : xxx = 30, 27, 24, 21
  E32_TTL_2W : xxx = 33, 30, 27, 24
  */
  configuration.OPTION.transmissionPower = POWER_30; // Device power

  /* WOR period is called WAKE_UP time here
  WAKE_UP_xxx with xxx = Wake-Up Period
  xxx = 250, 500. 750, 1000, 1250, 1500, 1750, 2000
  */
  configuration.OPTION.wirelessWakeupTime = WAKE_UP_1250; 

  /*
  AIR_DATA_RATE_000_03     0.3 kb/s
  AIR_DATA_RATE_001_12     1.2 kb/s
  AIR_DATA_RATE_010_24     2.4 kb/s
  AIR_DATA_RATE_011_48     4.8 kb/s
  AIR_DATA_RATE_100_96     9.6 kb/s
  AIR_DATA_RATE_101_192   19.2 kb/s
  AIR_DATA_RATE_110_192   19.2 kb/s
  AIR_DATA_RATE_111_192   19.2 kb/s 
  */
  configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate

  /*
  UART_BPS_RATE_xxx with xxx = Baudrate
  xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
  */
  configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate

  // Set configuration changed and set to hold the configuration
  ResponseStatus rs = e32ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); // WRITE_CFG_PWR_DWN_LOSE to not save permanently

  Serial.println(rs.getResponseDescription());
  Serial.println(rs.code);
  c.close();
}

 

Acknowledgement

I would like to thank Renzo Mischianti for the wonderful libraries and the detailed tutorials.

12 thoughts on “Using LoRa with the EByte E220, E22 and E32 series

  1. Thank you for the very, excellent documentation.

    Please share tinySA Ultra cofiguration for Ebyte E220-900T30D. did you use triggering? Have watched Eric’s youtube videos, feeling a little overwelmed; especially with triggering.

    [url=https://drive.google.com/file/d/16ueK_zGvQOtzUgejC9PCGnO1Dyy_-rdm/view?usp=sharing]I managed once to capture preamble and message.[/url]

    My settings were not recorded; best I can recall: Start 916.1 Mhz., Stop 916.3 Mhz,. Center 916.125 Mhz,. Span .300 Mhz. Used tinySA desktop app and OBS to capture video.

    Wonder if “the smoke is out”of module. Able to config modules using Ebyte RF software without any issue.

    1. Hi William,
      I agree, there’s lots of options for the setting. I spent quite some time until I got what I wanted. Fortunately, there is an option to save the settings. Since I do not use this nice device every day, I will have forgotten next time how I got it working.
      So the main settings are:
      Frequency: Start: 845 MHz / Stop: 900 MHz
      Level: Auto
      Trigger: Normal / Level: -60 dBm / Edge: Down / Trigger: PRE / Interval: 0s
      Trace: Enable
      Measure: – 3dB WIDTH
      Mode: HIGH in

      What I noticed is that one should not set the frequency limits too wide. I don’t say it’s the optimum settings – it’s just what works for me.

      Hope this helps!

  2. Is it possible that module E32 , E22 and E220 will work together. What I need to setup to be able send and revive packages on all of them

  3. Are you able to send the full payload size when in fixed point transmission mode? I’ve been testing and the modem seems to consider the first 3 bytes as part of the payload when they aren’t received on the other end.

    I’ve also noticed that in order to send multiple packets, the payload must have the address/channel at every new subpacket, the transmission while in transparent mode seems a lot easier to operate considering this.

    1. I have tested this now with an E22 module, which has a maximum payload size of 240 bytes. Indeed, I was only able to send 237 bytes, the rest is used for address and channel as you rightly say. Thanks for the useful comment!

      1. Thanks for checking my work, these modules have some interesting undocumented behavior.

        I wonder if that data is really transmitted as part of the payload, and if the receiving device uses that “header” info to know if it should handle the rest of the frame. That still doesn’t explain why the channel would be part of that, and I’m honestly a bit confused why this has to be specified when I don’t think it sets the receiving channel. I can see some use in sending a packet across each channel or something, but then it would still only be ready to receive on a single channel.

        1. I also don’t know why the channel should be sent. However, the three bytes are used for the channel and the address. It’s mentioned in the data sheet.

  4. Is there any way to know the address a packet was received from while in fixed point mode?

    1. The sender address is – to my best knowledge – not sent. A pragmatic solution would be if you add the address to the message you.

      1. Thanks, I bought a few of these modules a few weeks ago, and I believe some rylr 998 modules were aware of the receive address, but are a bit less compatible.

        I’ve tested the e22 and e220’s talking to each other, the only issue is that the e22’s have the option for a network ID while the 220’s don’t seem to have that.

        The maximum subpacket size is 240b on the e22 and 200b on the e220, so encoding the sender address in here seems like a waste of space and potentially unreliable. I’m wondering if the SPI versions reveal info about the sender.

Leave a Reply

Your email address will not be published. Required fields are marked *