Big ESP32 + SX127x topic part 3

Related thread (external): ESP32 40Mhz vs 26MHz XTAL

(But does not give much details about differences.)

(Ah, I overlooked @Verkehrsrot’s last post.)

I think that the menu entry mainly configures the clock multiplicator/divisor and that Bluetooth and WiFi require a speed of at least 80 MHz. The XTAL reference is somewhat misleading.

Whoever designed the the Heltec WiFi LoRa 32(V2) board definition probably knew that this board only exists with a 40MHz crystal and designed the entries accordingly:

  • 240 MHz = 40 MHz × 6
  • 160 MHz = 40 MHz × 4
  • 80 MHz = 40 MHz × 2
  • 40 MHz = 40 MHz × 1
  • 20 MHz = 40 MHz ÷ 2
  • 10 MHz = 40 MHz ÷ 4

Compare this to the Heltec WiFi LoRa 32 board, which has a 26 MHz crystal.

  • 240 MHz = 26 MHz × 9
  • 160 MHz = 40 MHz × 6
  • 80 MHz = 26 MHz × 3
  • 26 MHz = 26 MHz × 1
  • 13 MHz = 26 MHz ÷ 2

The higher frequencies are approximations only and are off by 2.5%.

1 Like

I actually didn’t notice but the Heltec V1 and V2 indeed have different crystals.

I was not aware before that ESP32 can be used with CPU frequencies lower than 80MHz (it’s just that WiFi/BT are not supported anymore then).
Have not been using Arduino IDE for a long time and PlatformIO does not show these values in a nice dropdown. Previously those lower frequencies were not listed (not yet supported).

The low CPU frequencies appear interesting for low-power applications.
Do you have any experience with using the ESP32 @ 10MHz (or 13MHz)?
Does it draw substantially less power than the default 80MHz?
Do you possibly have any comparison figures available?

Crystals used on some popular ESP32 LoRa boards:

Most of them use a 40MHz crystal.

Board Crystal frequency
Heltec WiFi LoRa 32 V1 26MHz
Heltec WiFi LoRa 32 V2 40MHz
TTGO LoRa32 V2.0 40Mhz (integrated in ESP32-PICO-D4)
TTGO T-Beam V0.5 40 MHz

(Will need input from others for more recent versions of TTGO LoRa32 and T-Beam.)

No, I have never tried it.

Anybody else?

ESP32 power consumption at different CPU speeds

I conducted some basic power consumption tests with an ESP-WROOM-32 module on a bare breakout board, and a simple test script using the Arduino framework.

Script: Switch a GPIO port between high and low state with a 5s interval (delay(5000)) and write state information to the serial port.
Tested with CPU frequencies 240MHz, 160MHz, 80MHz, 40MHz and 10MHz.

The tests ran fine with correct timing.
(As expected but I had not tested this with lower ESP32 CPU frequencies before).

ESP-WROOM-32 module supply current consumption:

CPU frequency ESP-WROOM-32 supply current
240 MHz 37.7 mA
160 MHz 27.8 mA
80 MHz 21.6 mA
40 MHz 11.9 mA
10 MHz 8.0 mA

(The ESP-WROOM-32 module used for these tests is an older version with ESP32 gen 0.)

The tests show that lowering the CPU frequency results in substantial power savings.
This looks promising for ESP32 low-power applications that do not need WiFi or Bluetooth (but it will require a board that respects low-power usage).

Note that use of WiFi and Bluetooth requires a CPU frequency of at least 80MHz.

The test setup was kept very basic to minimize influence from external components. The CPU load was small. Results for real applications may vary and it will be interesting to see low CPU frequency results from others.


ESP32 and ESP8266 vulnerabilities

1 Like

TinyPICO low-power friendly tiny ESP32 board

Deep sleep: 18 uA

A nice board but with a price tag of $20 plus $15 for shipping it is quite expensive for what you get.

I am having a an issue with a Heltec V2 board. Using the example sketches to test as recommend.
In ABP mode everything functions fine.
In OTAA mode I can see the device register in the TTN console but the device never seems to get a joined response and the registration in the TTN console repeats over and over. (lighting bolt symbol)
As a test I connected an external RFM95 (used different pins for nss and di01,di02) this works perfectly in OTAA mode. So it has be something with the onboard radio ?
Pin setup as below for the default configuration.
const lmic_pinmap lmic_pins = {
.nss = 18,
.rst = 14,
.dio = { 26, 35, 34 },
.rxtx_rx_active = 0,
.rssi_cal = 0,
.spi_freq = 0

Any help with this odd behaviour would be greatly appreciated.
(If this is the wrong place to post this please relocate as necessary)

The most obvious causes would be:

  • Incorrect mapping of DIO pins
  • Incorrectly entered LoRaWAN keys/id’s

When ttn-abp.ino appears to work correctly (do you see the messages arriving at the console?) that at least shows that the SPI connection to the SX1276 is functioning.

Assuming that you have used the exact same ttn-otaa.ino sketch (with same LoRaWAN keys/id’s defined) for both the on-board SX1276 and the external RFM95, that would rule out that the keys/id’s are entered incorrectly as they do work with the external RFM95.

Is the board explicitly labeled V2 on the PCB (next to the antenna connector)?
If not, then it’s a V1.x board and you should try .dio = { 26, 33, 32 } instead.

If it is a true V2 board then the pin mapping as you described is correct. In which case there might be something wrong with the board, either a connection problem between ESP32 and SX1276 DIO pins or a faulty SX1276.


Yes I can see messages in the console in abp mode.

Correct exact same sketch.

Yes 100%

So I can only assume its a faulty board.

While we are at it could you explain the difference in the use of the DIO pins in adp vs otaa mode

Many thanks for the help

There should not be any difference. LMiC uses the same radio functions for both.

What is critical is that the DIO that indicates transmit complete, and especially receive success correctly change and be seen. But that is critical for any downlink in either mode.

If I remember correctly: DIO1 is needed by LMIC for handling of downlink messages. For OTAA, DIO1 is required to be able to join. A succesful join is required to send normal uplink messages.
Using OTAA, when DIO1 is not correctly wired or its LMIC pin-mapping is set incorrectly, the node will be unable to join and will be unable to send uplink messages (other than join requests).
When using ABP and DIO1 is not correctly wired/mapped this will not restrict the node from sending uplink messages (but downlink messages will fail).
So there actually is a practical difference related to DIO1 between OTAA and ABP.

As a result, when DIO1 is incorrectly wired/mapped, when using ABP the node can still successfully send uplink messages, while this is not possible when using OTAA.

I have succeded in flashing my TTGO with micro python firmware for ESP32
Micro Python works but does not include SX127x nor LMIC library.

Does somebody knows a LoRaWAN library for micro python ?

You could check the sources of LoPy. That is a ESP32 based devices supporting LoRaWAN.

At the top of the topic it states:
The LMIC-Arduino library can be found here: This was last updated 2 years ago.
Also on GitHub I find: This has more recent updates.
What is the reason for identifying the matthijskooijman repository; not the mcci-catena?

Good question. The reason is: changes over time while the topic start has not been updated (much) recently. The mcci-catena version was already included in a future ‘part 4’ of this topic (to be released later). Based on your response I have updated the library information in the current topic start.

For information about available LoRaWAN libraries see: Overview of LoRaWAN Libraries [HowTo]

1 Like

At the top of this topic, under LoRa Performance, it is said:

The LoRa performance of the Heltec and TTGO boards has shown to be sub-optimal. Important factors are the quality of the RF circuitry (design) and the antennas.

There is a things network gateway some 300m distant from my node. It reliably receives uplink data. But about half of downlink data packets were lost. My node was often not hearing the gatweay.

For my TTN node I have a TTGO LoRa32 V1 with the small helical antenna pictured here.

To optimize performance I have found:

  • the antenna is best laid horizontal rather than
    stood upright
  • a virtical quarter wave stub should be attached at the antenna mount.

This picture shows the arrangement

With these optimizations, the uplink SNR values are improved on average by about 10dB, and the downlink SNR is improved by about 11dB (LMIC values divided by 4). Both uplink and downlink are now adequately reliable. Typically uplink SNR is about +6dB and downlink SNR is about +3dB (LMIC values divided by 4)


You can try .
Only ABP is supported.

Heltec Wifi LoRa 32 V2 - I2C issues

While checking the board’s pinout diagram and its pin definitions (defined in the ESP32 Arduino Core) I noticed that the default SDA and SCL (I2C) pins are defined incorrectly which can cause problems.
This is explained below.

ESP32 by default uses GPIO21 and GPIO22 pins for I2C but also has the possibility to remap the I2C interface to different pins. Heltec Wifi Lora 32 V2 does not use the standard I2C pins for its OLED display and uses GPIO4 and GPIO15 instead. The values of SDA and SCL should be defined correspondingly but they are not. The values of SDA and SCL are incorrect.

SDA and SCL are defined as pin 21 (GPIO21) and pin 22 (GPIO22), which is common for ESP32.
But for connecting the display they used pins GPIO4 and GPIO15 instead.
Vext (which is actually ‘Vext control’) controls (enables/disables) Vext external power.
Like SDA, Vext is defined as GPIO21 which is weird because it conflicts with SDA.

The following is defined in the board’s pins_arduino.h file (part of ESP32 Arduino Core):

static const uint8_t SDA = 21;          /* CONFLICT! */
static const uint8_t SCL = 22;
static const uint8_t SDA_OLED = 4;
static const uint8_t SCL_OLED = 15;
static const uint8_t Vext = 21;         /* CONFLICT! */

When using the Arduino Wire (I2C) library, the ‘Wire’ object (which controls the I2C hardware interface) is initialized with Wire.begin(). Without parameters, this will initialize the I2C interface using the pins defined by SDA and SCL. To use different pins for I2C, the pins have to be explicitly specified: Wire.begin(sda_pin, scl_pin).

Vext and SDA are mutually exclusive. One GPIO cannot be used to control both Vext external power and SDA at the same time, but unfortunately that is how the pins are defined! The values of SDA and SCL cannot be changed in application code because they are defined in the ESP32 Arduino Core as const int.

During I2C communication the SDA and SCL interface pins will switch between HIGH/LOW with a frequency of 100+ kHz. If GPIO21 is used for SDA then Vext power will also switch with 100+ kHz accordingly. This is not good and the Vext voltage regulator is not designed for that.
Therefore GPIO21 must not be used for SDA but unfortunately that is exactly what happens if Wire.begin() is called without parameters.

To use pins GPIO4 and GPIO15 for I2C instead (which is how the on-board display is wired) use:

Wire.begin(/*sda*/ 4, /*scl*/ 15);


The ESP32 Wire library will remember the sda_pin and scl_pin parameters that were specified in the first Wire.begin(…) call. If any 3rd-party library later calls Wire.begin() (without parameters) that luckily does not cause the pins used for I2C to be reset to the default SDA and SCL pins.
Wire.begin(sda_pin, scl_pin) must be called before calling any third-party library initialization code (e.g. bme280.begin()).

The ESP32 supports two separate I2C hardware interfaces. The second interface is accessible via the Wire1 object which can be initialized as follows:

Wire1.begin(alternate_sda_pin, alternate_scl_pin)

I2C is a shared bus that can be shared by many peripherals/sensors. In most cases a single I2C interface will be sufficient and the second I2C bus will not be needed.

About calling Wire.begin() from within third party library code

The I2C interface is a shared bus that can be used by many peripherals. Different types of peripherals will require different (third party) libraries. Initializing a shared bus is therefore the responsibility of the application and not the responsibility of third party libraries, which should therefore not try to initialize it (i.e. should not call Wire.begin()).

In the early Arduino days only Atmel 8-bit MCU’s were supported. These only had a single I2C interface on fixed pins. But when Arduino matured, the framework was ported to other MCU architectures like ESP32 which supports 2 hardware I2C interfaces that can be mapped to different pins. If third party libraries then call Wire.begin() without parameters this can introduce problems.

Unfortunately, many libraries (including from Adafruit) call Wire.begin() (without parameters) from library code and do not provide a mechanism for specifying which pins to use for I2C so they will always use the pins defined by SDA and SCL - unless the application initializes the Wire object (I2C interface) first. If the real (mapped) I2C pins are different from what is defined by SDA and SCL the application has to call Wire.begin(sda_pin, scl_pin) before any third party library initialization code is called.