MiniPill LoRa - STM32 Low Power Node

I would like to share my new STM32 node. This node is based on a STM32L051C8T6 microcontroller. With a BME280 environment sensor it uses 1.9 uA in sleep mode.

Short list of features:

  • Arduino code is used, combined with a few lines of STM32 HAL code;
  • It’s a generic development board, hence the name MiniPill, a wink to the BluePill board. Without the RFM95 chip it can be used for other projects;
  • The LMIC library is used for the LoRaWAN protocol with OTAA as connection setup instead of ABP;
  • Due to this library it is possible to downlink data to the node and get confirmation of the received data;
  • In Low Power Mode: 1.9 uA during sleep period.
  • With a BME280 sensor beside temperature and humidity also pressure can be measured and send through LoRaWAN.
  • Enough pins are available for other sensors or actuators;
  • Due to a 4 layer SMT print and SMT components the print is smaller but has an other form-factor. The size is 33.1 mm x 22.9 mm.
  • Pinouts are labeled and 4 VCC and GND connectors are available for external sensors.


Please checkout this post on my blog:



Hello Leo,
I believe that this is the first true open source + open hardware, do-it-yourself lorawan client using the ST32L family.
And It is really low power, small, and multi-purpose.Really a great lorawan client!

I ordered some prototypes from JLCPCB, partially assembled as from your BOM… great!
I am also a fan of PlatformIO for STM32 development, using mixed libraries.

On PlatformIO I installed your board+variant, another great achievement, and successfully compiled your code.
This is the footprint:
RAM: [=== ] 27.1% (used 2216 bytes from 8192 bytes)
Flash: [======== ] 83.3% (used 54576 bytes from 65536 bytes)
So we can’t use anything less than a 64k flash cpu.

Did you try another PlatformIO + STM32 combination for development?
Waiting for JLCPCB, I would like to prototype your setup using a bluepill on another nucleo STM32L board.

1 Like

Hello Andrea,

Thank you for your enthusiasm for my MiniPill. I also use the STM32CubeIDE for some development. Actually you can use the CubeMX part to setup the clock. I discovered I use the MSI clock, but you can switch easily to the more accurate HSI clock. Look at the variant.cpp file and change the content of the WEAK void SystemClock_Config(void) function with the clock config generated by CubeMX.
I really like the possibility that I can use STM32 HAL to tweak low level settings.
Please keep me informed about your experience with my MiniPill LoRa.

With kind regards,


Actually the STM32L051, 07x, 08x etc are an extremely common pairing with LoRaWAN, including open source LoRaWAN. Because this is the processor core used in the “Murata module” it shows up in places such as the Arduino Mkrwan, the MCCI Catena, etc.

That’s not to diminish an individual effort, just to point out that the reasons that this is a fairly good choice have been somewhat widely recognized.


After designing and building two nodes I could measure, calculate and compare the power consumption. I have made a post to give some considerations about power consumption on LoRa nodes:
Here an example of the power calculations of the MiniPill LoRa:


With the MiniPill LoRa I’ve made a Co2 and environment meter with LoRa capability!


Please checkout this blog:


these are very interesting projects. Currently I try to rebuild/copy the first one, presented by @leo_korbee.
I had a look to the linked webpage and further to the git repo. I see that also the “STM32LowPower” library is used. I have a question regarding this library in combination with “MCCI LoRaWAN LMIC library”:

My idea is to have OLED display in addition. Pressing a button, the STM32 wakes up, activates a display and shows the current measurements. I tried step by step and could succeed with all individual tasks and could combine most of them, like:

  • measuring DHT22 & battery, sending these values via Lora (using “MCCI LoRaWAN LMIC library”) and switch into a sleep moode until next transmission
  • measuring DHT22 & battery, go to sleep mode, wake up by pressing a button and displaying the values on a OLED.

However, when I want to combine these two things I run into a problem as soon as I introduce the interrupt wakup in the setup() function by:

// wakeuppin is a GPIO
// wakeuptask is a callback function where a bool will be set
// RISING is the condition of the signal
LowPower.attachInterruptWakeup(wakeuppin, wakeuptask, RISING);

As soon as the LMIC is intialized an error occurs (serial monitor) stating

	.pio\libdeps\genericSTM32F103C8\MCCI LoRaWAN LMIC library\src\lmic\radio.c:923

Does anybody know if the combination of “MCCI LoRaWAN LMIC library” and “STM32LowPower” using the “attachInterruptWakeup()”-function can work together?

Otherwise, I could also try starting from the code of @leo_korbee, who does not use “MCCI LoRaWAN LMIC library” (as far as I understand) and modify a bit for waking up and displaying the values.

Best regards!

Hallo pallago,
I do not use the interruptmodes yet, but I do have the same idea to make a node with switch. First of all: do you have a debounce circuit between your switch and the wakeup pin. Due to bouncing the interrupt routine might run several times and delay the LMIC actions.
Second thought is that a few routines in radio.c calls hal_disableIRQs, maybe there is a problem.
I’m waiting for debounce circuits max6816/18 before I wil try to use the Interrupt :-).
You can try the proprietary library for LoRaWAN, this code does not use interrupts and is straightforward.
Best regards!

Hello leo_korbee,

thanks a lot for the reply. I used a 100nF capacitor for debouncing between pin and GND. Most probably there are some better circuits to avoid debouncing, I just ordered some max6816 - thanks for the advice :wink:

OK, I will try the way you did; using the proprietary library for LoRaWAN- “only” ABP as far as I see, but it should be perfectly fine - at least for my data. :slight_smile:

imho a “hardware” - debouncing circuit consists at minimum of a seriall resistor, a capacitor to GND and a schmitt-trigger input. This gives you a “low-pass” characteristic of the input and limits the current flowing thru the switch when actuating.

It makes no sense to debounce in hardware; do it in software by ignoring the input for some period of time after first detection.

Basically, when your wakeup fires, disable it. Don’t re-enable it until going back to sleep. If the purpose of waking up was to activate a power consuming display or send a LoRaWAN message, the system is going to be awake for a few seconds, which well exceeds bounce time. Simply not re-arming the wakeup interrupt until going back to sleep should solve it. If you’re going to sleep the processor in between (eg with the display on or waiting for receive windows) then make things stateful and don’t re-activate the button wakeup until you are done with the timed wakeup.


Dear @leo_korbee,
I am sorry to bother again. Since you have the same idea regarding a switch I think you will encounter soon the same ‘problem’.
Side remark: I could successfully use and change your code according to my requirements. My STM32 works and transmits the data via ABP to the things network.

Problem: The attachInterruptWakeup; as soon as I introduce it into the code accoding to the example " ExternalWakeup.ino" by STM32LowPower the sending procedure (lora.Send_Data(Data, Data_Length, Frame_Counter_Tx)) stucks. The additional required code for an wakup via button is:

volatile bool wakeupbool = false;
// Pin used to trigger a wakeup
#ifndef USER_BTN
#define USER_BTN PA15
// LowPower:
const int wakeuppin = USER_BTN;

void wakeuptask(){
wakeupbool = true;


pinMode(wakeuppin, INPUT_PULLUP);
// critical code
LowPower.attachInterruptWakeup(wakeuppin, wakeuptask, RISING);


Without the “critical code” your program works fine. Any idea why the data transmission stucks?

Dear @pallago,
At this moment I have no clue. Maybe when I see all the code. Maybe you can send it in a PM or GitHub reference. In a few day the holidays are starting and I have some time to try it for myself. Thanks for the possible pitfalls. Good job you get it running on a STM32F103 - Bluepill I guess.
Regards, Leo.

Hello cslorabox,
Thanks for the tips.I’m a hardware engineer and hence rather solve issues first in hardware. Maybe not the smartest and cheapest way but I try to avoid multiple interrupts and timing issues in software. It sometimes take a lot of time to debug those issues.
Regards, Leo.

Hello @Leo_korbee and @pallago,
I am working on a project similar to yours, I am building a LoRaWan node with a stm32f103c8t6 bluepill and rfm95, I use the MCCI_LoRaWAN_LMIC_library library with the ttn-abp example, I am trying to add a push button that does the function to send an uplink every time I press the button, instead of each TX_INTERVAL, but I have not obtained success, could you guide me or share an example that may be of help to me,
thanks in advance.

Hello @dvalencia,
I will do some experiments in a few days, maybe I’ll get a solution.
With regards.

Dear @leo_korbee and @dvalencia,

thanks a lot for the replies. Indeed I use a bluepill a (genericSTM32F103C8) board. I provide here a (minimal) code where you (if you like) could see where the problems occurs. Some comments:

  • The ABP works if LowPower.attachInterruptWakeup is removed in the code, i.e.
    if you comment line 60 (LowPower.attachInterruptWakeup) you see that sending via ABP works, the BluePill switched into LowPower mode and wakes up periodically.

  • Low Power wakeup works only if the lora.Send_data is removed in the code, i.e.
    if you comment line 89 (lora.Send_Data(...)) the BluePill sleeps and can be woken up by pressing a button.

Maybe this helps a bit as a starting point. But don’t take too much time; I thought about implementing a displaying method when starting the BluePill, i.e., using the setup() function, i.e. just need to reset the BluePill, then the code in the setup() is executed once.

#include <Arduino.h>
// Low Power for sleeping
#include <STM32LowPower.h>
#include "LoRaWAN.h"
#include "STM32IntRef.h"
// configuration of ABP parameters/keys ...
#include "secconfig.h" 

// RFM95W connection on MiniPill LoRa
#define DIO0 PB10
#define NSS  PA4
RFM95 rfm(SPI, DIO0, NSS);

// define LoRaWAN layer
LoRaWAN lora = LoRaWAN(rfm);
// frame counter for lora
unsigned int Frame_Counter_Tx = 0x0000;

// LowPower
// Sleep this many microseconds.
#define SLEEP_INTERVAL 60000
// Declare it volatile since it's incremented inside an interrupt
volatile bool wakeupbool = false;
// wakeup: Pin used to trigger a wakeup
#ifndef USER_BTN
#define USER_BTN PA15
// wakeup: define pin for waking up
const int wakeuppin = USER_BTN;

// wakeup - LowPower
// this function is a callback function; it will be called when the wakeup is done
// try to avoid any delays or operations which takes time (see library example of lowpower)
void wakeuptask(){
  wakeupbool = true;

void setup() {
  // Serial monitor for debugging:
  Serial.println("test hello");
  //Initialize RFM module
  lora.setKeys(NwkSkey, AppSkey, DevAddr);
  Serial.println("Initialize RFM module done");

  // LowPower: Configure low power at startup
  // LowPower: Set pin as INPUT_PULLUP to avoid spurious wakeup
  pinMode(wakeuppin, INPUT_PULLUP);
  // LowPower: Attach a wakeup interrupt on pin, calling repetitionsIncrease when the device is woken up
  // cirtical code: use interrupt: lora.Send_Data will stuck
  LowPower.attachInterruptWakeup(wakeuppin, wakeuptask, RISING);
  Serial.println("LowPower init done");

  // use this delay for first packet send 8 seconds after reset

void loop() {
  Serial.println("In the loop");

  // define bytebuffer
  uint8_t Data_Length = 0x09;
  uint8_t Data[Data_Length];
  // fill Data with useful data later, e.g. with sensor data
  for (int i=0; i<Data_Length; i++){
    Data[i] = i;

  if (wakeupbool){
    // here something should ONLY happen when wakeup is done
    // e.g. displaying a value
    Serial.println("I woke up");
    wakeupbool = false;
  // LORA, send the data
  Serial.println("I will send");
  //lora.Send_Data(Data, Data_Length, Frame_Counter_Tx);
  Serial.println("I have sent");
  // LORA, increase framecounter by 1

  // take SLEEP_INTERVAL time to sleep
  Serial.println("I sleep now");
  Serial.println("I woke up AFTER sleep");

I use PlatformIO as environment, the platform.ini file looks like:

platform = ststm32
board = genericSTM32F103C8
framework = arduino
upload_protocol = serial
monitor_speed = 9600
lib_deps = 
	; Low Power
	stm32duino/STM32duino Low Power @ 1.0.3

Hello friends,
I managed to create some example code with the LMIC library and a button as wakeup device. I used a small debounce circuit. Here is the link to the example:

A picture showing the very low power consumption in deepSleep mode: 0.5uA!


Have fun!


Worth keeping in mind that you’re working on different processors.

While they’re both ARM cores from ST Micro, @pallago has an STM32F103 which is a fairly early one with a lot of details different than the more recent and LoRaWAN-appropriate STM32L0’s. IMHO questions about an STM32F103 are a bit tangential to this thread.

Dear @pallago,
I cannot see whats’s missing of wrong in your code. Mind the remark @cslorabox wrote, you are running code on a different controller than I do. Some features on your controller can be different, especially on interrupt handling. Did you try another pins for your button?

Here my working example code for the proprietary LoRaWAN library also you used in your code. It runs on my MiniPill LoRa (STM32L051C8T6 controller).

This code is very responsive, you should mind to use debouncing hardware/software.
With regards, Leo.

1 Like