(More Info) |
In my quest for reliable long range RF communication, I purchased some time ago a couple of EBYTE E32-915T30D wireless modules.
Note the documentation e32-915t30d_usermanual_en_v1.7.pdf shows them as E32-900T30D or E32-915T30D. It is the same module. The module is able to operate over the US 902-928MHz band and typically comes preconfigured for operation at 915MHz.
These modules support the Lora prototol, and use a Semtech SX1276 and an amplifier to produce up to 1W of RF power. That is quite substantial and well above what the SX1276 chip itself can do by itself (+20dBm or 100mW).
Another feature of these modules, compared to the RFM69 described elsewhere on this website is that they include a microcontroller that takes care of the SX1276 configuration and also provides a simpler UART interface.
As a result, the E32-915T30D is not fully customizable, you can program the center frequency and a few parameters but you do not have access to the plethora of registers available in the SX1276 itself. It is both a curse and a blessing. The UART is a bit simpler than the SPI interface, and the modes that are provided are very useful and cover the vast majority of common use cases, but you cannot set the module for more exotic operation. Another advantage is that you can preconfigure the modules and save the new configuration in the module itself such that they can be used very simply as serial port replacement in your application without the configuration code.
In this experiment, I mated them with a pair of ESP32 modules I had on hand https://www.aliexpress.us/item/3256806655173296.html. These modules have the reference HW-724 and include a small OLED display (64 x 128 pixels, monochrome) which makes them convenient for a field test.
The code takes advantage of a library available on GitHub LoRa_E32_Series_Library
The library simplifies the process of configuring the device, but is not necessary or even used once the module is configured. This is helpful because the E32 datasheet is poorly translated from Chinese and the library developer obviously took care of handling the missing details.
The E32 module supports 4 operating modes selected with 2 pins. For this example, I only use mode 0 (normal operation) and mode 3 (sleep/configuration). Mode 1 and 2 are useful for battery (low power) operation where the receiver is placed on a low power mode which requires the transmitter to use a longer preamble to make sure the receiver gets the message.
Since I only need mode 0 and mode 3, I connected the pins M0 and M1 on the E32 module together and used a single IO pin the ESP32. Another ESP32 pin is wired to the AUX pin on the E32 in addition to the Tx and Rx. The AUX pin goes low when the E32 is busy sending or receiving and can be used to wake up the microcontroller when a message arrives. Power can be obtained from the 5V point on the ESP32 but be careful that at full power, the E32 draws about 700mA, which will be too much for most computers USB ports, so this example code configures the module for lower power which keeps the current around 300mA (when transmitting). That should work with any computer or laptop.
Here is the complete pin-out between the E32 and the ESP32:
E32-915T30D ----- ESP32 with OLED HW-724
M0 ----- GPIO12
M1 ----- GPIO12
TX ----- GPIO13
RX ----- GPIO15
AUX ----- GPIO14
VCC ----- 5v
GND ----- GND
The code below first collects the E32's current configuration and prints it through the Serial.print debug facility of the Arduino IDE in the setup() section of the code. It also initializes the OLED display using the very friendly Adafruit library.
Then it goes into the loop() section of the code where it displays the received messages both on the OLED and the Serial.print debug port, and for the Tx build, it sends a new message every 2 seconds.
/*
* Lora Tx/Rx with E32-915T30D demo
*
* Uses mischianti LoRa E32 and Adafruit's GFX and SSD1306 libraries.
*
* https://www.mischianti.org/2019/10/29/lora-e32-device-for-arduino-esp32-or-esp8266-configuration-part-3/
*
* Added definitions for ESP32 and 915MHz operation (original code was apparently tested with a 433MHz module on Arduino UNO)
* ko4bb.com 8Nov2024
*
* E32-915T30D----- ESP32 with OLED HW-724
* M0 ----- GPIO12
* M1 ----- GPIO12
* TX ----- GPIO15
* RX ----- GPIO13
* AUX ----- GPIO14
* VCC ----- 5v
* GND ----- GND
*
*/
// select one
#define TRANSMITTER false
// configure LoRa library
#define ESP32 // this defines HARDWARE_SERIAL_SELECTABLE_PIN and turns off ACTIVATE_SOFTWARE_SERIAL
#define FREQUENCY_915
#define E32_TTL_1W // to correctly map the power levels in configuration.OPTION.transmissionPower
#include <LoRa_E32.h> // see statesNaming.h for other definitions
#include <HardwareSerial.h>
// for OLED
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// pinout of E32 module on ESP32-WROOM:
#define RX1_PIN 15 // goes to TxD pin of E32
#define TX1_PIN 13 // goes to RxD pin of E32
#define M0_M1_PIN 12
#define AUX_PIN 14
// OLED stuff
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // 9 Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display( SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET );
// hardware serial port to the E32
HardwareSerial myserial( 1 );
LoRa_E32 e32ttl100( RX1_PIN, TX1_PIN, &myserial, AUX_PIN, UART_BPS_RATE_9600, SERIAL_8N1 );
void printParameters( struct Configuration configuration );
void printModuleInformation( struct ModuleInformation moduleInformation );
String incomingString;
#if TRANSMITTER
const long sendInterval = 2000; // 2 seconds
int msgNbr=0;
char msgID[] = "LoRa Tx";
#else
char msgID[] = "LoRa Rx";
#endif
char dspBuf[80];
char msgAck[] = "ACK";
long previousMillis = 0;
/*---------------------------------------------------------------------------
* FUNCTION: setup()
*---------------------------------------------------------------------------*/
void setup() {
int i=0;
Serial.begin( 115200 );
// OLED
Wire.begin( 5, 4 );
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if( !display.begin( SSD1306_SWITCHCAPVCC, 0x3C, false, false )) { // Address 0x3D for 128x64
Serial.println( F( "SSD1306 allocation failed" ));
for(;;); // Don't proceed, loop forever
}else
Serial.println( F( "SSD1306 allocation succeeded" ));
display.display();
delay( 2 );
display.clearDisplay();
display.setTextSize( 1 ); // Normal 1:1 pixel scale
display.setTextColor( WHITE ); // Draw white text
display.setCursor( 1, 1 ); // Start at top-left corner
display.println( msgID );
display.display();
delay( 500 );
// E32 initializations
pinMode( M0_M1_PIN, OUTPUT );
pinMode( AUX_PIN, INPUT_PULLUP );
myserial.begin( 9600, SERIAL_8N1, RX1_PIN, TX1_PIN );
do{
// wait for AUX pin to go high, it goes low ~12mS after power up and stays low typically 600mS
i++;
}while( digitalRead( AUX_PIN == 1 ));
Serial.printf( "i=%d\n", i );
delay( 10 );
digitalWrite( M0_M1_PIN, 1 ); // go to mode 3 (sleep/configuration)
delay( 10 );
// Startup all pins and UART
e32ttl100.begin();
ResponseStructContainer c;
c = e32ttl100.getConfiguration();
// It's important get configuration pointer before all other operation
Configuration configuration = *(Configuration*) c.data;
//Serial.println( "Configuration:" );
Serial.println( c.status.getResponseDescription() ); // getResponseDescriptionByParams()
//Serial.println( c.status.code );
if( c.status.code == E32_SUCCESS ){
Serial.println( "Current Configuration: " );
printParameters( configuration );
// Default configuration (module as bought):
// HEAD : 11000000 192 C0
// AddH : 0
// AddL : 0
// Chan : 15 -> 915MHz
// SpeedParityBit : 0 -> 8N1 (Default)
// SpeedUARTData : 11 -> 9600bps (default)
// SpeedAirDataRate : 10 -> 2.4kbps (default)
// OptionTrans : 0 -> Transparent transmission (default)
// OptionPullup : 1 -> TXD, RXD, AUX are push-pulls/pull-ups
// OptionWakeup : 0 -> 250ms (default)
// OptionFEC : 1 -> Turn on Forward Error Correction Switch (Default)
// OptionPower : 0 -> 20dBm (Default)
// ----------------------------------------
// if we got the configuration, we can certainly get the module information
Serial.println( "Module Information" );
myserial.write( 0xC3 );
myserial.write( 0xC3 );
myserial.write( 0xC3 );
previousMillis = millis();
while( (millis() - previousMillis) < 100 ){ // wait no more than 100mS
if( myserial.available() ){
incomingString = myserial.readString();
strcpy( dspBuf, incomingString.c_str() );
ModuleInformation moduleInfo = *(ModuleInformation*)dspBuf;
printModuleInformation( moduleInfo );
break;
}
}
}else{
Serial.println( c.status.getResponseDescription() );
}
// set new configuration
configuration.ADDL = 0x0;
configuration.ADDH = 0x1;
configuration.CHAN = 0x0F; // frequency = 900 + chan, 0x0F -> 915MHz
configuration.OPTION.fec = FEC_0_OFF;
configuration.OPTION.fixedTransmission = FT_TRANSPARENT_TRANSMISSION;
configuration.OPTION.ioDriveMode = IO_D_MODE_PUSH_PULLS_PULL_UPS;
// Transmit Power
// Note: running 17dBm (low0) power mode for development so that it can run off the USB port on the computer
// At full power (1W or 30dBm) the E32 draws about 700mA and will need a separate 5V supply.
configuration.OPTION.transmissionPower = POWER_27;
configuration.OPTION.wirelessWakeupTime = WAKE_UP_1250;
configuration.SPED.airDataRate = AIR_DATA_RATE_011_48;
configuration.SPED.uartBaudRate = UART_BPS_9600;
configuration.SPED.uartParity = MODE_00_8N1;
Serial.println( "\nSaving configuration..." );
ResponseStatus rs = e32ttl100.setConfiguration( configuration, WRITE_CFG_PWR_DWN_LOSE ); // configuration settings are volatile
Serial.println( rs.getResponseDescription() ); // Serial.print( "Success" );
digitalWrite( M0_M1_PIN, 0 ); // return to mode 0
printParameters( configuration ); // Serial.print( "----------------------------------------
// HEAD : 10100111 167 A7
//
// AddH : 1
// AddL : 0
// Chan : 15 -> 915MHz
//
// SpeedParityBit : 0 -> 8N1 (Default)
// SpeedUARTData : 11 -> 9600bps (default)
// SpeedAirDataRate : 11 -> 4.8kbps
// OptionTrans : 0 -> Transparent transmission (default)
// OptionPullup : 1 -> TXD, RXD, AUX are push-pulls/pull-ups
// OptionWakeup : 100 -> 1250ms
// OptionFEC : 0 -> Turn off Forward Error Correction Switch
// OptionPower : 1 -> 17dBm
// ----------------------------------------")
c.close();
#if TRANSMITTER
Serial.println( "Device is Sending Messages" );
#else
Serial.println( "Device is Receiving and Acknowledging" );
#endif
} // setup()
/*---------------------------------------------------------------------------
* FUNCTION: loop()
*---------------------------------------------------------------------------*/
void loop() {
// always receiving
if( myserial.available() ){
incomingString = myserial.readString();
display.clearDisplay();
display.setCursor( 1, 1 ); // Start at top-left corner
display.println( msgID );
display.setCursor( 1, 12 );
display.println( incomingString );
display.display();
if( strcmp( incomingString.c_str(), msgAck ) == 0 ){
Serial.println( "Received ACK" );
}else{
sprintf( dspBuf, "Received -%s-", incomingString.c_str() );
Serial.println( dspBuf );
myserial.print( msgAck );
}
delay( 5 );
incomingString.trim();
}
delay( 10 );
#if TRANSMITTER
if( (millis() - previousMillis) > sendInterval ){
sprintf( dspBuf, "Msg Number %d", msgNbr++ );
if( msgNbr > 999 )
msgNbr = 0;
int j = 0;
while( dspBuf[j] != 0 )
myserial.write( dspBuf[j++] );
display.clearDisplay();
display.setCursor( 1, 1 ); // Start at top-left corner
display.println( msgID );
display.setCursor( 1, 12 );
display.print( dspBuf );
display.display();
Serial.printf( "Sent packet %d\n", msgNbr );
previousMillis = millis();
}
#endif
} // loop()
/*---------------------------------------------------------------------------
* FUNCTION: printModuleInformation()
*---------------------------------------------------------------------------*/
void printModuleInformation( struct ModuleInformation m ){
Serial.println( "----------------------------------------" );
Serial.print( F( "HEAD: " )); Serial.print( "0x" ); Serial.println( m.HEAD, HEX );
Serial.print( F( "Frequency: " ));
switch( m.frequency ){
case 0x32:
Serial.println( "433MHz" );
break;
case 0x38:
Serial.println( "470MHz" );
break;
case 0x45:
Serial.println( "868MHz" );
break;
case 0x44:
Serial.println( "915MHz" );
break;
case 0x46:
Serial.println( "170MHz" );
break;
default:
Serial.println( "Unknown" );
break;
}
Serial.print( F( "Version: " )); Serial.print( "0x" ); Serial.println( m.version, HEX );
Serial.print( F( "Features: " )); Serial.print( "0x" ); Serial.println( m.features, HEX );
} // printModuleInformation()
/*---------------------------------------------------------------------------
* FUNCTION: printParameters()
*---------------------------------------------------------------------------*/
void printParameters( struct Configuration configuration ){
Serial.println( "----------------------------------------" );
Serial.print( F( "HEAD : ")); Serial.print( "0x" ); Serial.println( configuration.HEAD, HEX );
Serial.println( F(" "));
Serial.print( F( "AddH : ")); Serial.println( configuration.ADDH, BIN );
Serial.print( F( "AddL : ")); Serial.println( configuration.ADDL, BIN );
Serial.print( F( "Chan : ")); Serial.print( configuration.CHAN, DEC ); Serial.print(" -> "); Serial.println( configuration.getChannelDescription() );
Serial.println( F( " "));
Serial.print( F( "SpeedParityBit : ")); Serial.print( configuration.SPED.uartParity, BIN ); Serial.print( " -> " ); Serial.println( configuration.SPED.getUARTParityDescription() );
Serial.print( F( "SpeedUARTData : ")); Serial.print( configuration.SPED.uartBaudRate, BIN ); Serial.print( " -> " ); Serial.println( configuration.SPED.getUARTBaudRate() );
Serial.print( F( "SpeedAirDataRate : ")); Serial.print( configuration.SPED.airDataRate, BIN ); Serial.print( " -> " ); Serial.println( configuration.SPED.getAirDataRate() );
Serial.print( F( "OptionTrans : " )); Serial.print( configuration.OPTION.fixedTransmission, BIN ); Serial.print( " -> " ); Serial.println( configuration.OPTION.getFixedTransmissionDescription() );
Serial.print( F( "OptionPullup : " )); Serial.print( configuration.OPTION.ioDriveMode, BIN ); Serial.print( " -> " ); Serial.println( configuration.OPTION.getIODroveModeDescription() );
Serial.print( F( "OptionWakeup : " )); Serial.print( configuration.OPTION.wirelessWakeupTime, BIN ); Serial.print( " -> " ); Serial.println( configuration.OPTION.getWirelessWakeUPTimeDescription() );
Serial.print( F( "OptionFEC : " )); Serial.print( configuration.OPTION.fec, BIN ); Serial.print( " -> " ); Serial.println( configuration.OPTION.getFECDescription() );
Serial.print( F( "OptionPower : " )); Serial.print( configuration.OPTION.transmissionPower, BIN ); Serial.print( " -> " ); Serial.println( configuration.OPTION.getTransmissionPowerDescription() );
Serial.println( "----------------------------------------" );
} // printParameters()
There is only one source file for both the Tx and Rx function. As shown above, it is for the receiver. For the Transmitter, simply adjust one line as shown below:
#define TRANSMITTER false
to:
#define TRANSMITTER true
Here is the traffic displayed on the logic analyzer during the getSonfiguration phase:
And here is the setConfiguration phase:
Here is a scope picture taken while sending a message. It shows the current drawn by the module: