LILYGO T5 4.7-inch E-paper ESP32 development board

One of the biggest advantages of ESP32 development boards (without even counting the speed, flash size, WiFi, Bluetooth, and two cores) is that they come in a variety of shapes and sizes: bare boards, with OLED, with color LCD, with LoRa chipset, with GPS modem, etc., etc. And finally, you can get them with an e-paper display.

LILYGO T5 4.7-inch E-paper ESP32 development board
Continue reading “LILYGO T5 4.7-inch E-paper ESP32 development board” »

How to generate PPM signal with ESP32 and Arduino

The PPM protocol for encoding Remote Control channel values is now a legacy. Still, it is widely accepted by different hardware and when tinkering with Arduino, remote control, and working on own accessories for flight controllers, PPM is still a valid option.

A few years ago I presented a code that allows generating PPM stream using Arduino and AVR hardware. That solution is very hardware-specific and works only with ATMega microcontrollers.

During my work of ESP32 DiyMotionController I stumbled on a problem: if ESP32 is not compatible with AVR when timers are a concern, how to generate a PPM stream on ESP32?

PPM signal on an oscilloscope

This is why I reserved one evening, powered up my oscilloscope (Rigol DS1054Z), and created the code you can see below.

    #define PPM_FRAME_LENGTH 22500
    #define PPM_PULSE_LENGTH 300
    #define PPM_CHANNELS 8
    #define DEFAULT_CHANNEL_VALUE 1500

    #define OUTPUT_PIN 14

    uint16_t channelValue[16] = {1500};

    hw_timer_t * timer = NULL;

    enum ppmState_e {

    int getRcChannel_wrapper(uint8_t channel)
        if (channel >= 0 && channel < 16)
            return channelValue[channel];
            return DEFAULT_CHANNEL_VALUE;

    void IRAM_ATTR onPpmTimer() {

        static uint8_t ppmState = PPM_STATE_IDLE;
        static uint8_t ppmChannel = 0;
        static uint8_t ppmOutput = LOW;
        static int usedFrameLength = 0;
        int currentChannelValue;


        if (ppmState == PPM_STATE_IDLE) {
            ppmState = PPM_STATE_PULSE;
            ppmChannel = 0;
            usedFrameLength = 0;

        if (ppmState == PPM_STATE_PULSE) {
            ppmOutput = HIGH;
            usedFrameLength += PPM_PULSE_LENGTH;
            ppmState = PPM_STATE_FILL;

            timerAlarmWrite(timer, PPM_PULSE_LENGTH, true);
        } else if (ppmState == PPM_STATE_FILL) {
            ppmOutput = LOW;
            currentChannelValue = getRcChannel_wrapper(ppmChannel);

            ppmState = PPM_STATE_PULSE;

            if (ppmChannel > PPM_CHANNELS) {
                ppmChannel = 0;
                timerAlarmWrite(timer, PPM_FRAME_LENGTH - usedFrameLength, true);
                usedFrameLength = 0;
            } else {
                usedFrameLength += currentChannelValue - PPM_PULSE_LENGTH;
                timerAlarmWrite(timer, currentChannelValue - PPM_PULSE_LENGTH, true);
        digitalWrite(OUTPUT_PIN, ppmOutput);

    void setup()
        pinMode(OUTPUT_PIN, OUTPUT);
        timer = timerBegin(0, 80, true);
        timerAttachInterrupt(timer, &onPpmTimer, true);
        timerAlarmWrite(timer, 12000, true);

    void loop()
        Here you can modify the content of channelValue array and it will be automatically
        picked up by the code and outputted as PPM stream. For example:
        channelValue[0] = 1750;
        channelValue[1] = 1350;

the code uses one of ESP32 Timers/Alarm to generate PPM in the background. Logic happens inside of onPpmTimer handler function. Code rescheduled the timer alarm to trigger according to RC channel values. The given example allows to encode 8 channels in a PPM stream. It's fully asynchronous from the main loop.


ESP32, Arduino and Timer/Alerts

Because of completely different architecture, ISR and Timer solutions known from other Arduino compatible platforms, especially AVR/ATmega does not work on ESP32. They don't. At all. If you would like to port any code that uses timers from AVR Arduino to ESP32 Arduino, you would have to rewrite them completely.

However, timers and alarms with espressif ESP32 Arduino core are simple. Much simpler than AVR equivalents! No more writing registers with magical numbers.

The basic code for Arduino ESP32 Timer looks like this:

hw_timer_t * timer = NULL;

void IRAM_ATTR onTimerHandler() {
    //This code will be executed every 1000 ticks, 1ms

void setup()
    // Prescaler 80 is for 80MHz clock. One tick of the timer is 1us
    timer = timerBegin(0, 80, true); 

    //Set the handler for the timer
    timerAttachInterrupt(timer, &onTimerHandler, true); 

    // How often handler should be triggered? 1000 means every 1000 ticks, 1ms
    timerAlarmWrite(timer, 1000, true); 

    //And enable the timer

void loop() {
    //Here you can do whatever you want

You have to bear in mind that:

  1. timer = timerBegin(0, 80, true); If you run ESP32 with a different clock, you have to modify the prescaler. 80 is valid for 80MHz clock
  2. timerAlarmWrite(timer, 1000, true); the last true sets alarm to autorepeat. If you set it false, handler will be triggered only once and you will have to manually set it to enabled inside of the handler
  3. If you want to synchronize data/variable between the ISR/timer handler and the main loop, variable has to be declared as volatile
  4. To make it better, you should also use semaphores for data synchronization. Then, the code would look like this:
volatile int counter = 0;

void IRAM_ATTR onTimerHandler() {
    //This code will be executed every 1000 ticks, 1ms

void loop() {
    counter = counter + 5;

For more information refer to Espressif ESP32 documentation.

ESP32 and multiple I2C buses

One of the advantages of the ESP32 microcontrollers over the competitions is dual-core architecture and two I2C buses.

Yes, the I2C bus allows connecting multiple slave devices to single pair of SCL SDA wires. As long as slave device addresses are unique, everything will work just fine: OLED display, LM75 temperature sensor, MPU6050 gyroscope. However, one has to remember that one of the devices can be polled at a time. If the bus is used by, for example, MPU6050, parallel communication with SSD1306 OLED display is not possible.

Here comes the advantage of having multiple I2C buses. Not only you can have more devices of the same type and avoid I2C address conflicts, but you can also take full advantage of 2 ESP32 cores and communicate with multiple devices at the same time.

ESP32 with OLED

In the following example, I will show how to use multiple I2C buses of ESP32 using Espressif core for Arduino.

#include <Wire.h>

//Set pins for I2C1
#define I2C1_SDA_PIN 21
#define I2C1_SCL_PIN 22

//Set pins for I2C2
#define I2C2_SDA_PIN 0
#define I2C2_SCL_PIN 23

TwoWire I2C1 = TwoWire(0); //I2C1 bus
TwoWire I2C2 = TwoWire(1); //I2C2 bus

void setup()
    // Init buses
    I2C1.begin(I2C1_SDA_PIN, I2C1_SCL_PIN, 100000); // Start I2C1 on pins 21 and 22
    I2C2.begin(I2C2_SDA_PIN, I2C2_SCL_PIN, 100000); // Start I2C2 on pins 0 and 23

void loop() 
    //Talk to device connected to I2C1

    //Talk to device connected to I2C2

Two remarks:

  1. ESP32 use matrix IO that allows mapping any pin to any function
  2. GPIO 34, 35, 36 and 39 are INPUT ONY ports and can not be used as SCL or SDA
  3. Some Arduino compatible libraries do not allow to set I2C bus to use always use I2C1. Refer to the library documentation for details on how to choose I2C bus

QmuBeacon – ESP32 and LoRa based GPS recovery beacon and locator

Having a broken leg, limited mobility, and paid sick leave have some perks after all. I've finally found some free time to sit on the project I started, I think, two years ago: ESP32, LoRa based GPS beacon, and locator. You know, put it on an airplane and have a way to get the distance and bearing when you crash.

ESP32 LoRa GPS beacon and locator

In the last few days, I finally managed to:

  • Improve RF handling and make it more robust as well as send more data from the beacon
  • The Locator can work as a beacon too
  • Multi-beacon support – single Locator can track multiple beacons at once
  • Some other fixes and improvements

The future is also bright since, if time allows, I intend to integrate it with INAV as well. But now, it will not act the same way as INAV Radar. I have different ideas for that. That might be interesting, right?

If anybody would like to talk about the project, here is the Discord channel for that

Espressif ESP32

ESP8266, ESP32 and ESP32-S2 comparison

Currently, there are 2 types of MCU from Espressif on the market: ESP8266 from 2014, ESP32 from 2016 and ESP32-S2 that debuted in the second half of 2019. Although ESP32-S2 is not yet very popular, it is expected it will replace ESP8266 as a cheaper, more powerful and more secure alternative in the upcoming years.

The table below compares the main features of ESP8266, ESP32 and ESP32-S2

ESP8266 ESP32 ESP32-S2
MCU Xtensa L106 Xtensa LX6 Xtensa LX7
Number of cores 1 2 1
Number of bits 32 32 32
Clock frequency 80MHz 160MHz 240MHz
Coprocessor No Yes Yes (RISC-V)
WiFi 802.11 b/g/n 802.11 b/g/n 802.11 b/g/n
Bluetooth No BT 4.2 BR/EDR & BLE No
RAM 160kB 520kB 320KB
Internal flash No ESP32‑D2WD Only – 2MB No
External SPIFlash Up to 16MB Up to 16MB Up to 1GB
GPIO 17 36 43
SPI 2 4 4
I2C 1 (software) 2 2
I2S 2 2 1
UART 2 3 2
ADC 1 18 20
ADC resolution 10-bit 12-bit 12-bit
DAC No 2 2
DAC resolution 8-bit 8-bit
Software PWM 8 16 8
SDMMC interface No Yes No
Temperature sensor No Yes Yes
Touch sensors No Yes Yes
CAN No 1 No
Ethernet MAC No 1 No
Espressif ESP32

Espressif ESP32-S2

When Espressif release ESP32 WiFi & Bluetooth capable MCUs back in 2016, many things changed in the DIY and tinkerers community. We finally had a cheap MCU that could do real IoT stuff and was easy to use. However, the cheap ESP32 is not really cheap in relative terms. Sure, it is cheap, but there are cheaper solutions. ESP8266 for example.

Yes, the old ESP8266 that lacks any built-in security and which power consumption is not that low even in a deep sleep mode. It was expected that Espressif would, sooner or later, offer an improved replacement for ESP8266. Cheaper than ESP32, but with features that ESP8266 lacked. Continue reading “Espressif ESP32-S2” »

ESP32 vs ESP8266

ESP8266 and ESP32 – the main differences

ESP8266 and ESP32 are the next best thing that happened to DIY world since Arduino itself. Thanks to development boards based on those MCUs brand new possibilities opened in front of all DIY and tinkering enthusiasts. With those two, not only we have cheap and powerful microcontrollers, but we can also make them talk to other devices via WiFi and Bluetooth.

ESP32 development board

Continue reading “ESP8266 and ESP32 – the main differences” »

How to connect GPS to ESP32

Thanks to a very versatile Input/Output matrix, it is quite simple to connect NMEA GPS modules to ESP32 MCUs. Not only ESP32 boards have 3 serial ports you can choose from, they can be assigned to almost any pin you want.

ESP32 with GPS and OLED display

In this example we will connect a popular Ublox NEO-M8N like Beitian BN-880 or BN-220 to a ESP32 development board and output current position on a USB serial port using Arduino IDE and TinyGPS++ library. Let’s begin

Continue reading “How to connect GPS to ESP32” »

QmuBeacon – open-source Arduino GPS locator/beacon drone is now public

If too long didn’t watch: it’s a GPS equipped beacon/locator DIY system based on popular radio development boards compatible with Arduino: LoRa32u4 and ESP32 LORA32 working in 868/915MHz band using LoRa modulation. You put a beacon on anything you want to track: airplane, drone, car, boat, wife, husband and use Locator to get distance and heading. Both beacon and locator are battery operated and should have a range of a few kilometers in the air.