ESP32 - LoRa - OLED Module

Get the most out of your (heltec/ttgo/aliexpress) ESP32 LoRa OLED development board

Similar projects worth following
This project aims to revel the hidden secrets of this ESP32 WiFi Lora module. Not much details have surfaced yet about this board. Consider this as a forum to ask questions and get answers...

I have attached my current sample code to send and receive. It makes use of the Arduino LoRa Library by Sandeep Mistry and displays RSSI and SNR of the received data. 

The latest version (v1.80) of RadioHead RF95 Library also works now without additional tweaks! Prior versions failed to compile (missing atomic.h).

Since these modules don't have a tcxo, there is a slight frequency mismatch. RadioHeads 'frequencyError()' tells me, the relative offset between my modules to be roughly approx. 1.5kHz.  


good news: 868MHz (EU) / 915MHz (US) Version now avaliable on Aliexpress


There's a nice case created by Aleksei Golikov / lexgol for the heltec Wifi LoRa 32 board:

Just download the files and have it printed yourself.


updated LoRa client, showing current frequencyError() in the top right corner. RSSI and SNR in the top left corner.

JPEG Image - 1.54 MB - 10/23/2017 at 12:18



Waterfall Display showing sender and receiver with exact matching frequencies. Modulation: 15.6kHz Bandwidth, 4/8 Coding Rate, Spreading Factor 512 on 433MHz ISM Band

JPEG Image - 220.78 kB - 10/23/2017 at 11:12



lora sender and receiver - receiver showing rssi and snr

JPEG Image - 1.79 MB - 10/18/2017 at 14:16



Example LoRa sender sketch. RF modem configured for long range transmission on 434.5MHz

x-arduino - 2.63 kB - 10/18/2017 at 14:04



Example LoRa receiver sketch showing received packets, RSSI and SNR on integrated OLED

x-arduino - 3.08 kB - 10/18/2017 at 14:03


  • Module Testing & Some Useful Links

    reg.swensen4 days ago 0 comments

    Range Testing 

    Transmitter at my desk

    Testing the TTGO Modules

    I spent a couple of hours this morning gathering some data from my two TTGO ESP32-LoRa radios to try to determine the best settings to use to get reliable communications in weak signal conditions. For the purposes of these tests I needed a relatively poor signal path so I wouldn't have to walk too far to run out of signal. To this end I placed the module programmed as the transmitter on my desk right next to my computer. My desk is located in a cubicle on the second floor of a brick building so it is surrounded on 3 sides by the (mostly) steel cubicle walls in addition to the usual clutter of steel and brick. The remaining side (south facing) is next to a window that looks down the longest sidewalk on campus. 

    Signal Path 1
    1500ft path with no solid obstructions
    Signal Path 2
    Shorter path with significant obstructions

    Signal Paths

    I chose two signal paths for evaluation. The first is straight down the sidewalk outside my window 1500ft (457m) to the entrance of the performing arts center. Along this path there are no buildings that stand directly in the way however there are quite a few trees. The path has buildings on either side that are likely to provide for some multipath reflections. The second signal path is a more difficult one in terms of RF penetration. The far point of that path is about 750ft (228m) to the northwest of my office. Signals traversing this path must pass through the wall of my cubicle as well as that of two adjacent cubicles. In addition the signal travels through the brick wall of my building and passes through the corner of a nearby building on the way. This path has less multipath opportunities but greater signal attenuation than the first path. To make the observations I walked both paths while watching the received packets on the OLED screen of my second TTGO module.

    Test Settings

    For all of the tests the transmitter was set to a center frequency of 433.000mHz with the transmitter power level of 20dBm. Neither figure was verified as I don't have the test gear to do so. The bandwidth setting of the LoRa modulator was set to 125kHz and forward error correction was set at 8/4 for all tests. The antennas used were the 'coil spring' antennas supplied with the modules. Packets consisted of my Amateur Radio callsign followed by a space, a # character, and a sequential number. They varied in length from 8 to 11 characters. I varied the LoRa spreading factor from 7 to 11 and took readings of RSSI and reported SNR from the receiver at the far end of each path. The receive antenna was held at about 1 meter off the ground and oriented vertically to match the transmitter antenna. The data are shown below:

    Spreading factorRSSI (path 1)SNR (path 1)RSSI (path 2)SNR (Path 2)
    7 (128 chips/bit)-114 dBm13.25 dB-111 dBm14.5 dB
    8 (256 chips/bit)-109 dBm14.0 dB-113 dBm14.25 dB
    9 (512 chips/bit)-105 dBm15.5 dB-107 dBm16.25 dB
    10 (1024 chips/bit)-106 dBm16.5 dB-105 dBm16.75 dB
    11 (2048 chips/bit)-103 dBm17.75 dB-102 dBm17.25 dB
    RTL-SDR display of transmitted signal
    Signal as displayed by RTL-SDR (sf = 11, BW = 125Khz)


    While the reported signal levels and SNR figures all appeared to improve as I increased the spreading factor, I didn't see the amount of improvement that I might have been led to expect from reading Semtech's marketing literature. I also noticed that contrary to what one might intuitively expect, as the spreading factor was increased the reliability of packet receipt while moving (walking) went down considerably. By the time I got to sf = 11 there were practically no packets being received while moving but reception would resume immediately when I stopped and held the receiver still. I believe that this may have been the result of multipath distortion since it affected path 1 more than path 2 and was worse adjacent to buildings beside the path. The most reliable packet reception while moving was at sf = 7. In fact this was the only...

    Read more »

  • RadioHead library v1.81 update

    data11/15/2017 at 19:43 0 comments

    New RadioHead library v1.81 available.

    LoRa relevant updates: 

    - slow data rate for predefined ModemConfig with Sf 4096 enabled

    - AGC for all modes enabled

    Download from

    1.81 2017-11-15 RH_CC110, moved setPaTable() from protected to public.
    RH_RF95 modem config Bw125Cr48Sf4096 altered to enable slow daat rate in register 26 as suggested by Dieter Kneffel. Added support for nRF52 compatible Arm chips such as as Adafruit BLE Feather board, with a patch from Mike Bell.
    Fixed a problem where rev 1.80 broke Adafruit M0 LoRa support by declaring bitOrder variable always as a unsigned char. Reported by Guilherme Jardim.
    In RH_RF95, all modes now have AGC enabled, as suggested by Dieter Kneffel.

  • Bluetooth

    data10/25/2017 at 21:03 0 comments

    Included within the esp32 repository as well as the recently added one from heltec ( ) is an example for using Bluetooth. I just gave it a try but besides advertising its name via BLE there is nothing else supported yet :(

    One can not even pair this device yet, just update the advertised name. At least, this can be used to broadcast some sort of  data - e.g. temperature and humidity from a connected DHT22 or DS18b20... 

    Once espressif has their repository updated and full bluetooth/BLE support added, a number of interesting applications come to mind: e.g. a Bluetooth to LoRa bridge for a long range bluetooth chat.

  • RadioHead RF95 Driver / Low Data Rate Optimization

    data10/24/2017 at 03:28 1 comment

    Digging somewhat deeper into the RadioHead driver, I noticed an issue with the predefined ModemConfiguration parameters. The third configuration register (0x26) is always 0x00. This is, how they are defined in RH_RF95.cpp:

    PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] =
        //  1d,     1e,      26
        { 0x72,   0x74,    0x00}, // Bw125Cr45Sf128 (the chip default)
        { 0x92,   0x74,    0x00}, // Bw500Cr45Sf128
        { 0x48,   0x94,    0x00}, // Bw31_25Cr48Sf512
        { 0x78,   0xc4,    0x00}, // Bw125Cr48Sf4096

    Now, I found in the SX127x application note ( ) that the third register 0x26 has two functions: AGC and Low Data Rate Optimization. The later being mandatory for spreading factors >= 11

    So for a Sf of 11 (2048) or 12 (4096), it should be set. The predefined configuration for Bw 125kHz, Cr 4/8 with Sf 4096 should actually read    { 0x78, 0xc4, 0x08 }

    Also keep this in mind when creating your own configuration parameters.

  • RadioHead RF95 Driver - advantages:

    data10/23/2017 at 10:59 0 comments

    After some tweaking, I now drastically reduced the  offset (aka frequency mismatch) between my modules.

    Without any matching, I had a deviation of approx. 1.500Hz. Now, with dynamically adjusting the frequency based on the reported difference, I am able to keep the offset around +/- 10Hz. That's an improvement by a factor of  more than 100!

    Due to this improvement, I hope to make use of even lower bandwidths in the future.  Instead of the predefined ModemConfigs available in the RF95 driver,  Using setModemRegisters(), I've set my modules to a bandwidth of 15.6kHz, Coding Rate of 4/8 and a Spreading Factor of 512.  A quick look at gqrx confirmed the desired bandwidth and exact identical/matching frequency.

    I am pretty curious how much this will affect the range. Report follows once I have some time...

  • Battery Voltage Measurement

    data10/23/2017 at 10:58 2 comments

    ADC / Battery Voltage measurement:

    So far, I had no luck in getting proper voltage readings from one of the ADC inputs. Since there is no documentation, I simply queried all ADC pins in a hope to get one with some voltage readings. Here's part of the code: 


     int pinCount = 11;  int ADCpins[] = {2,12,13,32,33,34,35,36,37,38,39}; // these are the ADC pins

    float VBAT;  // battery voltage from ESP32 ADC read float ADC_divider = 250/30;  // voltage divider proportions - hypothetical so far :-)

    void setup(){

     for (int thisPin = 0; thisPin < pinCount; thisPin++) {

        Serial.print(thisPin, DEC);     Serial.print(" = ");     Serial.print(ADCpins[thisPin], DEC);     Serial.print(" => ");

        pinMode(ADCpins[thisPin], INPUT);

        VBAT = ADC_divider * (float)(analogRead(ADCpins[thisPin])) / 1024.0; // LiPo battery voltage in volts     Serial.print("Vbat = "); Serial.print(VBAT); Serial.println(" Volts"); }   


    And this is, what I get: 

    0 = 2 => Vbat = 0.00 Volts

    1 = 12 => Vbat = 0.00 Volts

    2 = 13 => Vbat = 0.00 Volts

    3 = 32 => Vbat = 3.52 Volts

    4 = 33 => Vbat = 1.16 Volts

    5 = 34 => Vbat = 0.00 Volts

    6 = 35 => Vbat = 0.00 Volts

    7 = 36 => Vbat = 0.00 Volts

    8 = 37 => Vbat = 0.00 Volts

    9 = 38 => Vbat = 0.00 Volts

    10 = 39 => Vbat = 0.00 Volts

    So I do get some reading on ADC pin 32 and 33 but it does not really make sense so far.

    The value remains more or less the same when I remove the battery...

View all 6 project logs

Enjoy this project?



borie.f wrote 11/01/2017 at 08:16 point

Hello data thank you, in fact i want to know if this tuning is specific for one shield or you must do a mesure for each shields (even if the shield is of the same brand/model). what is this tool who generate this information ? 

  Are you sure? yes | no

data wrote 11/01/2017 at 14:59 point

I can only guess, that you are referring to the frequency error?!
You can can obtain this value by calling frequencyError() from the RadioHead library after having received a packet.

Due to the use of cheap components, every module is supposed to have an offset. On the other hand, the LoRa protocol is robust enough to cope with even a large offset. According to manufacturer specs, the

Maximum tolerated frequency offset between transmitter and receiver, no sensitivity degradation, SF6 thru 12 is +- 25% of BW

Hence, the recommended min. bandwidth is 62.5kHz for modules without a TCXO.  This also gives you more than enough margin for the doppler effect, if your devices are on the move.


Returns the last measured frequency error. The LoRa receiver estimates the frequency offset between the receiver centre frequency and that of the received LoRa signal. This function returns the estimates offset (in Hz) of the last received message. Caution: this measurement is not absolute, but is measured relative to the local receiver's oscillator. Apparent errors may be due to the transmitter, the receiver or both.
ReturnsThe estimated centre frequency offset in Hz of the last received message. If the modem bandwidth selector in register RH_RF95_REG_1D_MODEM_CONFIG1 is invalid, returns 0.


  Are you sure? yes | no

borie.f wrote 10/31/2017 at 21:30 point

Hello, thank you for sharing your project. can you detail to me your parameters :

1/     #define spreadingFactor 9
// #define SignalBandwidth 62.5E3
2/    #define SignalBandwidth 31.25E3
3/    #define preambleLength 8
4/    #define codingRateDenominator 8

  Are you sure? yes | no

data wrote 10/31/2017 at 22:54 point

What kind of details do you want to know?

In brief, less bandwidth and higher spreading factor means slower datarate but longer range.

Have a look at the API documentation at

  Are you sure? yes | no

ia wrote 10/23/2017 at 14:38 point

Meybe add a sound modem?

for example using baofeng to increase distance.

i nead keyboard 4-5 buttons

and mesh or normal ax25 protocol on lora

  Are you sure? yes | no

data wrote 10/18/2017 at 14:07 point

Does anybody know to which ADC pin the LiPo circuit is connected? I'd like to display charging status as well...

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates