Buttons, Batteries and LoRaWAN

A project log for ESP32 LoRa GPS Bike Thing

ESP32-based tracker, LoRa beacon and road-quality monitor

usedbytesusedbytes 05/02/2020 at 16:471 Comment

Next up on my list was to get the buttons on the board working, and set it up to run on battery power.

The T-Beam board has three buttons:

Setting up the ESP32 for the USER button was simple, and I could basically use the code from the example: We just need to set-up IO38 as an input and enable a falling-edge interrupt:

static xQueueHandle gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void* arg)
    uint32_t gpio_num = (uint32_t)arg;
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);


void app_main(void)

    io_conf.intr_type = GPIO_PIN_INTR_NEGEDGE;
    io_conf.pin_bit_mask = (1ULL << GPIO_NUM_38);
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pull_up_en = 0;
    io_conf.pull_down_en = 0;

    gpio_isr_handler_add(GPIO_NUM_38, gpio_isr_handler, (void*)GPIO_NUM_38);

    int cnt = 0;
    uint32_t io_num;
    while(1) {
        if(xQueueReceive(gpio_evt_queue, &io_num, 1000 / portTICK_RATE_MS)) {
            printf("Button pressed. GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
        printf("cnt: %d\n", cnt++);

Getting the interrupt from the AXP192 for the power button took a little more work. The AXP192 sets the IRQ line high when an enabled interrupt event happens, and it stays high until all of the pending interrupt events have been cleared my writing to the status registers over i2c.

So, I needed to implement some new functionality in the axp192 library to let me set up the interrupt masks and process and clear the events.

I've pushed these to my branch:

axp192_err_t axp192_read_irq_mask(const axp192_t *axp, uint8_t mask[5]);
axp192_err_t axp192_write_irq_mask(const axp192_t *axp, uint8_t mask[5]);
axp192_err_t axp192_read_irq_status(const axp192_t *axp, const uint8_t mask[5], uint8_t status[5], bool clear);

 With those in-place, I could enable the "short press" button interrupt in the AXP192, and process it in the ESP32.

I've set it up so that when the power button is pressed, the ESP32 writes to the AXP192 to turn everything off:

static void power_off()
    // Flash LED
    axp192_write_reg(&axp, AXP192_SHUTDOWN_BATTERY_CHGLED_CONTROL, 0x6a);

    // Save whatever state needs saving...

    // Power off all rails.
    axp192_set_rail_state(&axp, AXP192_RAIL_DCDC1, false);
    axp192_set_rail_state(&axp, AXP192_RAIL_DCDC2, false);
    axp192_set_rail_state(&axp, AXP192_RAIL_LDO2, false);
    axp192_set_rail_state(&axp, AXP192_RAIL_LDO3, false);

    vTaskDelay(1000 / portTICK_PERIOD_MS);

    // Turn off.
    axp192_write_reg(&axp, AXP192_SHUTDOWN_BATTERY_CHGLED_CONTROL, 0x80);

    for ( ;; ) {
        // This function does not return

When it's off, a press of the power button powers the system up, which is hard-wired in the AXP192 "Mode A".

The battery charging functionality comes up in a sane default state, 4.2 V end voltage and ~700 mA charge current, so I left that alone.

So now everything is set to run off an 18650 cell, and I can charge it from the USB port.

LoRaWAN - The Things Network

With GPS, the buttons and the battery working, next was the LoRa radio.

I searched around a bit and found this project on Github:

With a little bit of fiddling I could get it to build in my project - but I had to use the dev branch to support my version of ESP-IDF.

Here's the pin configuration I used:

#define TTN_SPI_DMA_CHAN  2
#define TTN_PIN_NSS       GPIO_NUM_18
#define TTN_PIN_DIO0      GPIO_NUM_26
#define TTN_PIN_DIO1      GPIO_NUM_33

 I registered on The Things Network (TTN) - which is a LoRaWAN network using community-run gateways. After creating a new application and loading the uinque IDs onto my device, following the great Getting Started guide for ttn-esp32, I flashed the board, attached the antenna, and powered it up...

And then nothing really happened. The LoRaWAN coverage at my house must not be very good. After a bit of waving the board around, I did see one activation message pop up on the TTN console, but I couldn't get the device activated and sending data.

Thankfully, the TTN map shows a gateway not terribly far from my house, so I popped the board in my backpack and cycled a few miles up the road to see if I could get a better signal.

When I was nearby the gateway, I powered the board up and checked the console on my phone:

Success! (What's the payload?)

Honestly I'm a little surprised that the range isn't better, but there's probably a few contributing factors - it's very flat around here, which isn't good for radio propagation, and I'm sure my antenna isn't the best.

So LoRa works. Reading the TTN best practice guide my code is definitely non-compliant. The ttn-esp32 code only supports doing a brand-new activation (join) every time, which is exactly what the TTN guide says you shouldn't do.

The underlying LMIC library, which handles all of the LoRaWAN stack, does have functionality to pull out the session keys needed, but that's not exposed via ttn-esp32. There's some discussion about it in the issues:

There's also some example there of how to do it - so I'll be giving that a go in the future.

However, with the lack of LoRaWAN coverage at my house, I think I'll buy my own gateway before I spend any more time with the LoRa code.


PumpkinEater wrote 11/23/2020 at 16:16 point

Thanks for the interesting information.  I am using a TTGo T-Beam with uses the AXP192. I found that the power off interrupts is not triggered in case the battery voltage is below a certain value (about 3.48V) . Do you figured out as similar behavior with the AXP chip?

  Are you sure? yes | no