Home | pfodApps/pfodDevices | WebStringTemplates | Java/J2EE | Unix | Torches | Superannuation | | About Us
 

Forward Logo (image)      

Easy Very Low Power BLE in Arduino (2019) -- Part 1

by Matthew Ford 12th February 2022 (originally posted 14th December 2018)
© Forward Computing and Control Pty. Ltd. NSW Australia
All rights reserved.

Building Very Low Power BLE devices - 2019
made Easy with Arduino
No Android coding is required
Part 1 of 3

This project is superseded by Building Very Low Power BLE devices - 2022

Novice users can build BLE devices that can run continuously
for over a year on 2 x AAA batteries or a Coin Cell

Update: 12th February 2022 – This project is superseded by Building Very Low Power BLE devices – 2022 Also see the new section Help My Upload Failed for solutions to programming problems
Update: 26
th November 2019 – Added instructions for installing nRF5 Flash SoftDevice tool
Update: 24
th March 2019 – Rev 4 of pfod_lp_nrf52.zip added support for GT832E_01 module and nRFChipInfo
Update: 11
th February 2019 – Rev 3 of pfod_lp_nrf52.zip added high drive output modes and fixed timer bug
Update: 11
th February 2019 – Rev 2 of pfod_lp_nrf52.zip
Update: 16
th January 2019 – Added setSlaveLatency()
Update: 6
th January 2019 – Replaced BlackMagic Probe with less expensive, and less robust, Particle Debugger for programming

Introduction

Note: in Building Very Low Power BLE devices – 2022 the protection resistors in the programmer have been reduced to 100R (from 1K here) because the CMSIS-DAP modules had trouble programming via 1K. The DAPLink module did not have a problem. Also see the new section Help My Upload Failed for solutions to programming problems

This project has been superseded by Building Very Low Power BLE devices – 2022 because most of the programmers and nrf52832 modules mentioned here are not longer available. Also the Arduino addon in Building Very Low Power BLE devices – 2022, pfod_lp_nrf52_2022 Rev 6, allows for designs with 1/4 of the supply current (<20uA) and 4 times the tx speed. The board support is also more general and simplified. Just two boards cover all the nrf52832 bare modules.

This tutorial, Building Very Low Power BLE devices made Easy with Arduino, is Part 1 of 3.

Part 1Building Very Low Power BLE devices made Easy with Arduino, this one, covers setting up Arduino to code nRF52 low power devices, the programming module and measuring the supply current. It also covers specialized low power timers and comparators and debounced inputs and using pfodApp to connect to and control the nRF52 device.
Part 2A Very Low Power Temperature Humidity Monitor covers using a Redbear Nano V2 module and an Si7021 temperature/humidity sensor to build a low power battery / solar monitor. It also covers modifying the Si7021 library to be low power, tuning the BLE device to reduce its current consumption of <25uA and designing a custom temperature/humidity display for your mobile.
Part 3A Redbear Nano V2 Replacement covers using other nRF52 based modules instead of the Nano V2. It covers selecting supply components, construction, removing the nRF52 chip programming protection, using NFC pins as normal GPIO, and defining a new nRF52 board in Arduino.

This tutorial is designed to allow the novice user to build very low power BLE devices, <100uA continuously both while waiting for a connection and while connected and sending/receiving data. All that is needed is familiarity with the Arduino IDE, some soldering proficiency and a multimeter. No Android coding is required.

This detailed tutorial uses Nordic Semiconductor nRF52832 chips and shows you how to code them using the Arduino IDE. It also covers how to connect to your BLE device from your Android phone and how to design custom menus and graphical displays.

Custom libraries are provided, based on Nordic's SDK and BLE support and Sandeepmistry's nRF5 IDE add-on and BLEPeripherial libraries. These libraries have been modified to support low power and to simplify use.

On the Android side, the tutorial uses a number of free Nordic Android apps, for testing and basic control. For custom Android displays, no Android coding is required. The free pfodDesigner Android app generates the low power Arduino code to display your own custom menus, charts, data logging etc on pfodApp. You can also build custom interactive graphical controls in Arduino code for pfodApp. No Android coding required, pfodApp handles all of that for you.

Quick Start

Wire up the programmer, install the low power support and use the free pfodDesigner to create a custom control menu/data logger and generate the low power sketch for pfodApp to connect to and display the controls and chart and log the data.

Boards and Programmers

Although Sandeepmistry's original nRF5 Arduino add-on supports a number of nRF51/52 boards, this project only supports nRF52832 chips. To get the lowest power you need to use a board with just the nRF52832 chip since any extra devices like accelerometers use more power.

The test device is a RedBear Nano V2. This is small enough to be used for real devices. The pfod_lp_nrf52.zip also covers stripped down modules like the the SkyLab Bluetooth Module SKB369 and GT832E_01, both of which are available from https://www.aliexpress.com Although the Nano V2 board includes a low power regulator, an external regulator is used so that the bare chip boards can be programmed also. This external regulator is also needed to ensure start up from very low current sources.

To program the nRF52832 chip the Particle Debugger is used. This probe also supports low level debugging if you ever decide to delve deeper into the Nordic SKD code, but all debugging done in this project just uses Arduino Serial print statements. As an alternative the BlackMagic Probe can be used. The construction and programming using the BlackMagic Probe is described here.

Tutorial Outline

Building the programming/test board
Installing the low power support for the nRF52832 in Arduino
How to Code for Low Power
Delays are evil. Use timers instead.
Measuring the Supply Current – Blink_millisDelay.ino
A Low Power Timer – Blink_lp_timer.ino
Extra Components Use Extra Power
Debugging Low Power
A Low Power BLE UART – lp_BLE_temp.ino
Sending data via lp_BLESerial
lp_comparator – lp_BLE_comparator.ino
lp_pinChange
High Drive Output Modes
nrf52ChipInfo
Low Power Button Debounce – lp_BLE_debounce.ino
Custom Low Power Control and Data Logging – lp_BLE_NanoV2_example.ino

Building the programming / testing board

The project uses the Particle Debugger to program and debug (via Arduino Serial prints) the nRF52832 chip. The Particle Debugger also supports GDB single step source level debugging, but that was not used in developing this low power library and you should be able to use the usual Arduino print statements to do any debugging you need for your sketch.

If you are only programming NanoV2 boards AND do not want to measure the supply current then you can just plug the NanoV2 directly into the Particle Debugger. Check the bottom of the Particle Debugger and match up the VIN and VDD pin names to the NanoV2 board. If you plug the NanoV2 in backwards you will damage it.

There is the schematic for the programming / testing board (pdf version).

Parts List

Approximate cost as at Dec 2018, ~US$80 including NanoV2, USB cables and USB supply (plus shipping)

Particle Debugger ~ US$20
USB extension Cable 1.5ft for the Particle Debugger ~US$2
SWD (2x5 1.27mm) Cable Breakout Board ~US$2 for Particle Debugger ribbon cable

SparkFun USB Mini-B Breakout (or Adafruit's Mini-B version) ~US$2 (OR you can use a Micro-B break out board and matching cable instead)
USB cable - A/MiniB – 3ft ~US$4 to suit SparkFun' Mini-B breakout board

USB supply ~ US$7 to power the nRF52832 chip via the breakout board and MAX8881EUT33 regulator

MAX8881EUT33+T 3v3 Digikey MAX8881EUT33+TCT-ND ~US$2
SparkFun SOT23 to DIP Adapter ~US$1

Redbear NanoV2 ~ US$17

2 x 10uF 25V Ceramic capacitors e.g. Digikey 445-7705-1-ND ~ US$2 mounted between tracks on copper side of vero board
1 x 0.1uF 50V Ceramic capacitor e.g. Digikey 478-10836-1-ND ~ US$0.5 mounted between tracks on copper side of vero board
1 x 0.1uF Ceramic capacitor e.g. Digikey 478-2472-ND ~ US$0.5
1 x 330R 1/4W 1% resistor e.g. Digikey S330CACT-ND ~US$0.1
1 x 1K5 1/4W 1% resistor e.g. Digikey RNF14FTD1K50CT-ND ~US$0.1
4 x 1K 1/4W 1% resistor e.g. Digikey RNF14FTD1K00CT-ND ~US$0.4
470uF 25V Low ESR capacitor e.g. Digikey 399-6127-ND ~ US$1
2 x 6 pin and 2 x 5pin female headers sockets e.g. Sparkfun PRT-11269 ~US$2 cut down the 8pin header to use for the Particle Debugger programming breakout board
6 x 6 pin male header pins e.g. Sparkfun PRT-00116 ~US$1.5
female to female jumper e.g. Adafruit ID: 1950 ~US$2
3mm x 12mm nylon screws, e.g. Jaycar HP0140 ~AUD$3
3mm x 12mm nylon tapped spacers, e.g. Jaycar HP0924 ~AUD$10
Vero board (strip copper) e.g. Jaycar HP9540 ~AUD$5
plastic sheet to insulate bottom of vero board e.g. cut out of plastic lid

The programmer/test board was constructed on vero board. The MAX8881 regulator provides 3.3V from the 5V USB supply. The Nano V2 has its own on board regulator, but if you want to be able to program bare modules like the SkyLab that don't include a regulator then you will need the MAX8881, or similar. The MAX8881 has a supply current of 3.5uA (typical), similar to the Nano V2 on-board regulator. When running your low power BLE project from a very low current source, then the Power OK (POK) and Shutdown (SHDN) pins on the MAX8881 can be used to hold off the nRF52 until the 470uF supply capacitor has charged up enough current to supply the chip's start up current surge.

In this circuit, the 470uF low ESR (Equivalent Series Resistance) is used to filter the chip's current pulses when it transmits. This allows you to use a multimeter to get a good idea of the average supply current. The supply current is measured across two shunt resistors in the GND line. For high currents, short out terminals 1 to 3 to remove the resistors. For medium currents short out terminals 2 to 3 to remove the 1K5 resistor. For the very low currents, that will be achieved in this tutorial, leave the terminals 1,2,3 open. The multimeter, or oscilloscope, is connected across the Current Monitor Test Points. If using an oscilloscope, connect the GND clip lead to the USB GND side of the shunt resistors.

The construction shown here was originally used for the BlackMagic Probe programmer so this programmer follows the same layout. The Particle Debugger ribbon cable and a header is used to program via the SWCLK and SWDIO and GND connections. A small header board is used to connect the TX, RX and GND for the Arduino Serial Debugging connection.

Unlike the BlackMagic Probe, which has a Vref connection to sense the supply voltage of the device being programmed, the Particle Debugger has a fixed 3.3V supply and no isolating buffers. This means the device being programmed should be powered by 3.3V when it is being programmed.

To protect both the device being programmed and the Particle Debugger from differing supply voltages, for example when one is powered and the other is not, 1K resistors are placed in series with the SWCLK, SWDIO, TX and RD leads. These limit the maximum current that can flow in-chip the I/O protection diodes.
NOTE: These resistors are now reduced to 100R because CMSIS-DAP modules had problems programming via the 1K resistors

In this board the NanoV2 was plugged in for ease of programming and measuring the supply current, before transferring to the final circuit. In general to program your nRF52, you need to connect the SWCLK, SWDIO, GND leads to the nRF52 and power the nRF52 with 3.3V (Vdd) while it is being programmed.

Installing the low power support for the nRF52832 in Arduino

This project builds on the Sandeepmistry's Arduino Core for Nordic Semiconductor nRF5 based boards and his BLEPeripheral library, but has been modified to add low-power support and to only work with Nordic's nRF52 series. It was tested using a Redbear NanoV2, nRF52832 board. The modified library provides a number of low power utilities, like sleep, lp_timer and lp_comparator. It also provides a general purpose low power Nordic UART BLE service, lp_BLESerial, which works with Nordic's free apps and with pfodApp. The free pfodDesigner app can be used to generate a low power Arduino sketch that will display a custom menu or graphical UI using pfodApp on your Android mobile with no Android programming required. See the pfodDesigner tutorials for more details on creating menus, sub-menus, charts and graphical UI's.

If you don't want to use pfodApp, you can still use the pfodDesigner to design a menu and then program the menu cmds into UART control in Nordic's nRF Toolbox. This project also uses Nordic's nRF UART v2.0 app for testing.

Download and Install the Arduino IDE

Here Arduino version 1.8.7 is used on a Windows 7 PC.

Install Sandeepmistry's Arduino Core for Nordic Semiconductor nRF5 based boards

1. Start the Arduino IDE
2. Go into File → Preferences
3. Add https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json as an "Additional Board Manager URL"
4. Open the Boards Manager from the Tools -> Board menu and install "Nordic Semiconductor nRF5 Boards". This project used version 0.6.0. Other version will have their own set of features and bugs.

NOTE: During installation it takes the Arduino IDE a few minutes to extract the tools after they have been downloaded, please be patient.

Adding the nRF5 Flash SoftDevice tool
(notes copied from Sandeepmistry's site)

1. cd <SKETCHBOOK>, where <SKETCHBOOK> is your Arduino Sketch folder:
OS X: ~/Documents/Arduino
Linux: ~/Arduino
Windows: ~/Documents/Arduino
2. Create the following directories: tools/nRF5FlashSoftDevice/tool/
3. Download nRF5FlashSoftDevice.jar to <SKETCHBOOK>/tools/nRF5FlashSoftDevice/tool/ (a local copy of nRF5FlashSoftDevice.jar is here)
4. Restart the Arduino IDE

Install the Particle Debugger driver

This project uses the Particle Debugger which needs a the mbed Window's Serial driver installed if you are using Windows 7, 8 or 9. Follow the instructions on that page. MacOS and Linux and Windows 10 should just work.

Once you have installed the mbed Serial driver, if needed, plug in the Particle Debugger. On Windows 7 you will see these drivers being installed.

The added COM port, shown as COM115 above, is the one you will select in Arduino to both program the board and use for the Arduino Serial Monitor.

Install the pfod_lp_nrf52 hardware support.

1. Download the pfod_lp_nrf52.zip file.
2. Start the Arduino IDE, Open the File → Preferences window an at the bottom find the directory where the
preferences.txt file is stored. In Window's 7 you can click on that path to open the directory in the Explorer.
3. From the preferences.txt directory, open the
packages sub-directory and then the sandeepmistry sub-directory which contains the hardware and tools directories.
4. Delete the
hardware directory.
5. Unzip pfod_lp_nrf52.zip to the
sandeepmistry directory to install the pfod low power support. This will install the modified hardware directory.
6. Close and restart the Arduino IDE.
7. Open the Tools → Board and scroll down to find the
pfod low power nRF52832 boards

Note: the boards with * against them do not use the selected Programmer. Instead they use their own pre-configured programmer. For example if you select the board *RedBear BLE Nano 2, and then use the IDE upload Arrow button, Arduino will program the board using pre-configured programmer (CMSIS-DAP) rather than the selected programmer. The Arduino IDE menu Sketch → Upload Using Programmer can be used to override this configuration.

Here we are using the *BLENano2 board which will use CMSIS-DAP as the programmer.

Connecting the Particle Debugger to the nRF52832 chip.

In order to program the nRF52832 you need to power it using 3.3V and connect the Particle Debugger GND, SWCLK and SWDIO pins to the chip. For debugging via Arduino print statements you also need to connect TX and RX to a pair of UART pins on the chip.

The small development board built above provides the 3.3V power, current measurement shunt resistors and serial debugging and can quickly completely disconnect the Particle Debugger. The Particle Debugger also provides code level debugging via this board, but it is not used in this project.

Programming the Nordic Softdevice

Before you can run a sketch you need to first load a 'softdevice' into the nRF52832 chip. The project uses the s132 softdevice, s132_nrf52_2.0.1_softdevice.hex (http://www.nordicsemi.com/eng/content/download/95151/1606944/file/s132_nrf52_2.0.0.zip) The pfod_lp_nrf52.zip already includes s132, so you don't need to download it from the Nordic website. However you still need to program it into your nRF53832 chip.

Plug in the NanoV2 module and the Particle Debugging programming cables and Serial jumpers and plug in the USB power supply to power up the NanoV2. Make sure the Current Shunt Shorting link in in place so the current sense resistors do not limit the supply volts while programming.

Start the Arduino IDE and make sure CMSIS-DAP is the selected programmer and that the mbed COM port is selected as the Port, i.e. COM115

Note Use the nRF52 Flash SoftDevice option half way down the Tools menu. Do not use the Burn Bootloader option at the bottom. The soft device flashed is the one selected under the Softdevice: The pfod_lp_nrf52.zip has pre-configured S132 as the softdevice for all the nRF52832 Boards in the pfod low power nRF52832 menu section.

NOTE: Arduino sometimes looses track of the COM ports. If you have problems uploading your sketch, close Arduino, un-plug the USB cable from the computer, restart Arduino and plug the USB back in. If there is an error recognizing the USB connection, restart the computer.

NOTE: If the flash of the SoftDevice fails, your nRF52 module may be protected against re-programming. See Removing the nRF52 coding protection flag and programming the sketch

How to Code for Low Power

The trick to getting a really low power solution is to do nothing most of the time, minimize the current through external pull-up/pull-down resistors on inputs and don't have any extra components.

Normally in Arduino you put all your action code in the loop() method which is then called repeatedly by Arduino, but this means most of the time the processor is just spinning around doing nothing.

Consider the loop() method in the Arduino Blink example (File → Examples → 01.Basic → Blink)

int led = 13;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second
}

Arduino just runs this loop() code for ever, setting the led on, delaying for 1sec and then turning it off.

Delays are evil. Use timers instead.

Looking inside the delay() method you will see

void delay( uint32_t ms ) {
  if ( ms == 0 ) {
    return ;
  }
  uint32_t start = millis() ;
  do {
  } while ( millis() - start < ms ) ; 
}

The do{ }while loop just spins using up processor time, and power, until the millis() counter has incremented by ms This is just a waste of time and prevents your sketch dealing with any triggers / inputs that occur.

Don't use delay()

For normal Arduino coding you should use a timer library, like millisDelay. See How to code Timers and Delays in Arduino for all the details.

Rewriting Blink.ino using millisDelay gives Blink_millisDelay.ino

#include <millisDelay.h>
// Pin 13 has an LED connected on most Arduino boards, including NanoV2
int led = 13;
bool ledOn = false;
millisDelay ledDelay;
const unsigned long DELAY_TIME = 1000; // ms == 1sec

// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);
  ledDelay.start(DELAY_TIME);
}

// the loop routine runs over and over again forever:
void loop() {
  // do other stuff here
  if (ledDelay.justFinished()) {
    ledDelay.repeat(); // start a repeat the delay
    ledOn = !ledOn; // toggle state
    if (ledOn) {
      digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    } else {
      digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    }
  }
}

Blink_millisDelay does not stop your loop() from running, rather it just checks each time if the ledDelay has timed out.

Upload the Blink_millisDelay.ino sketch on to the NanoV2, via the Particle Debugger.

Sometimes Arduino looses connection to the programming COM port. In that case try unplugging the Particle Debugger USB cable from the computer and plug it in again. If that does not work, close the Arduino IDE and restart it.

Measuring the Supply Current

Having programmed the Blink_millisDelay.ino sketch, lets measure the supply current.

To measure the supply current

Current shunt shorting link inserted while programming the NanoV2


1) With the current shunt shorting link inserted, upload the sketch onto the NanoV2.
2) Remove the USB cable supplying the Nano (black cable above) to reset the nRF52 (the NanoV2) after the programming is finished. This is necessary in order to measure the correct supply current.
3) Remove the Particle Debugger programming header and the Serial links (as shown above)
4) Move the current shunt shorting link so that it shorts out the 1K5 resistor leaving only the 330R resistor in the ground line. For very low current measurements just remove the link completely
5) Temporally short out the 330R resistor while re-connecting the USB power, then remove the temporary short to measure the voltage drop across the 330R resistor.

For the Blink_millisDelay.ino, sketch the supply current is about 6mA (The led on the NanoV2 draws a fraction of a mA). The voltage measured across the 330R current shunt resistor is ~2V. The voltage measured across the 330R current shunt resistor is ~2V, So the supply current is
2 Volts / 330 ohms == 0.006 Amps (i.e. 6mA)

The 2V across the current shunt resistor means there is only ~3V to run the NanoV2 . If the supply is much higher, i.e. 8mA, there not enough voltage left to power the chip. This sets the upper limit on the supply current that can be measured with a 330R shunt resistor.

NOTE: If the voltmeter reads 2.6V across the current measuring resistors than the nRF52 chip is not running, but is stuck in start up due to insufficient supply current.

Issue with NRF52 unexpectedly entering debug mode

NOTE: The nRF52 can un-expectedly enter debug mode while running, due to noise on the SWCLK line, resulting in a few mA of supply current instead of 100uA. See Issue with NRF51 unexpectedly entering debug mode
The solution appears to be to add a small value resistor say 470R and a small capacitor say 1nF in parallel between SWCLK pin and GND as close to the chip as possible and to disconnect any long traces connecting to SWCLK, after you have programmed the chip.
As a last resort you might even consider connecting a spare GPIO on the nRF51822 directly to SWCLK, and set the spare GPIO to output low after power up. This will prevent re-programming, so you might want to have some means for the application to release the pinOR cut the board connection between the GPIO and SWCLK

I tested a 1nF between SWCLK and GND and was unable to program the chip with the capacitor in place, but the chip continued to run and could be connected to. Even after removing the capacitor, the chip would not program. Re-flashing the Softdevice worked and made the chip programmable again. So do not add the suppression capacitor/resistor until after you have finished programming. My final solution is to connect a wire from SWCLK to GND, to ground it, after programming.

nRF52 Low Power Optimizations

You will see in the on-line nRF52 forums references to a) enabling the nRF52 DC to DC converter and b) disabling the Serial UART, to reduce supply current.
None of the sketches here use either of these optimizations.
The DC to DC converter option requires extra components, which the NanoV2 has but, which 'bare' nRF52 boards, like SkyLab, will not, so these sketches don't use it. As for disabling the UART, with this library, provided your sketch does not call Serial.begin(), the UART does not use any noticeable current, compared to the 100nA already being used. Finally, testing with current limited supplies shows that either of these optimizations result is higher start up current. See
Very Low Power BLE, Part 2 – Temperature Humidity Monitor for the details.

A Low Power Timer

As mention above, The trick to getting a really low power solution is to do nothing most of the time.

In the Blink_millisDelay.ino, above, most of the time the sketch is just calling loop() over and over again checking to see if the ledDelay has timed out. Only one loop() every second actually does anything useful, i.e turns the led on or off.

So to reduce the supply current we want to put the micro-processor to sleep until there is something to do.

Here is low power version Blink using lp_timer, Blink_lp_timer.ino

#include <lp_timer.h>
// Pin 13 has an LED connected on most Arduino boards, including NanoV2
int led = 13;
bool ledOn = false;
lp_timer ledTimer;
const unsigned long DELAY_TIME = 1000; // ms == 1sec

// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);
  ledTimer.startTimer(DELAY_TIME, handleLedTimer);
}

void loop() {
  sleep(); // just sleep here waiting for the timer to trigger
}

void handleLedTimer() {
  ledOn = !ledOn; // toggle state
  if (ledOn) {
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  } else {
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  }
}

After each DELAY_TIME, the ledTimer triggers and wakes up the loop() from the inside the sleep(). The sleep() method, on waking, calls the handleLedTimer method and then when sleep() exits, the reset of the loop(), if any, is executed. Finally when loop() is called again, it goes back to sleep waiting for the next trigger.

It is important to note that the handleLedTimer method is NOT an interrupt routine. Rather it is normal sketch method called from sleep() from with-in the sketch's loop() method. This means the handleLedTimer method has access to all the sketch's variables and methods, without needing to use volatile variables or any multi-tasking locks. This is true for all the handler methods used in this low power library.

Measuring the supply current for this sketch using the 1K5 + 330R == 1830R, i.e. with the shunt link completely removed, gives meter readings varying between 220mV and 480mV. The oscilloscope show a triangular wave between 200mV and 500mV, due to the led turning on and off and the draining and recharging of the 470uF supply capacitor. The average voltage is 375mA which implies an average supply current of 0.2mA (0.375V / 1830 ohms).

Increasing the DELAY_TIME to 5000 gives you measure the actual ledOn / ledOff current. The meter readings then are 610mV ledOn and 440mV ledOff. The oscilloscope shows a rounded square wave between 568mV ledOn and 68mV ledOff. That is 0.31mA ledOn and 0.037mA (37uA) ledOff.

So using sleep() and the lp_timer() has reduce the supply current from ~6mA to ~0.2mA and most of the 0.2mA supply current is due to the led. With the led Off, the supply current falls to 0.04mA, i.e. 150 time less, so you batteries will last 150 times longer.

The lp_timer class provides a means set a time out and call the handling method either once off, startDelay(), or repeatedly, startTimer(). As well as running the loop() method each time it times out.

The lp_timer uses the low frequency / low power clock, RTC, on the nRF52. The timer accuracy depends on the accuracy of low frequency clock. Some boards use the in-chip 32.768 kHz RC oscillator with an accuracy of about 1sec per hr at 25degC, while other boards like the RedBear NanoV2 have a 32.768Hz crystal with gives better accuracy.

In this library, the nRF52's RTC counter has been configured to give ms timings, with a maximum time out of ~68mins. Time outs greater than 4095000ms (68min 15 sec) will be limited to 4095000ms. The minimum timeout is 2ms. Requests for timeouts of 0ms or 1ms will result in a 2ms timeout.

For longer timeouts use an hour time out to count the hours in the handler and then at the end start a minutes, secs, ms timer to finish off. See the long_lp_timer.ino example which supports timers of up to 245,000 years.

Note: The millis(), micros() methods also uses the same timer as lp_timer. As a result the resolution of the micros() call is 244us. That is calls to micros() will increment in multiples of 244us.

Extra Components Use Extra Power

The above sketch also illustrates another low power design rule. Extra components, leds, sensors (accelerometers), etc, use power and when the basic chip is only using 40uA, even “low power” leds and sensors have a significant impact on the supply current. This is why you need to build your low power BLE project using a bare module rather than a development board.

The NanoV2 has minimal extra components, provided you ignore the led connected to D13. It includes an on-board a 3.3V regulator that uses ~4uA when you connect to the Vin pin instead of Vdd. The MAX8881 3.3V regulator used on this programming/test board uses about the same and its supply current is included in the measurements.

Debugging Low Power

The Particle Debugger provides a GDB debugging server, but that was not used in developing this library. For debugging you can use the usual Arduino Serial prints. The low power sketches shown here generally don't use Serial except for debugging, because the Arduino Serial connection uses noticeably more power.

One of the differences between the Particle Debugger, used here, and the alternative BlackMagic Probe programmer, is that the BlackMagic Probe install two (2) COM ports, while the Particle Debugger uses the some COM port for both programming and Serial.

When using the Particle Debugger to debug add Serial.begin(115200); to the top of the setup() and then open the Arduino Serial Monitor after programming using the same, programming, COM port , e.g. COM115 Leave the Serial headers plugged in to connect the NanoV2 to the Particle Debugger Serial leads. You can also leave the programming header plugged in while debugging.

If you are using the alternative BlackMagic Probe programmer, then to debug, add Serial.begin(115200); to the top of the setup() and then start another complete instance of the Arduino IDE and set its COM port to the second (higher numbered, eg. COM114) port and open the Serial monitor. Then you can continue to code and upload via the programming port (e.g. COM113) and check the debug output on the monitor output. Leave the Serial headers plugged in to connect the NanoV2 to the BlackMagic Probe Serial leads. You can also leave the programming header plugged in while debugging.

There is some sample output from Blink_lp_timer_debug.ino

The first loop() wake up at 261ms is due to starting the ledTimer. It is important to remember that the loop() is woken up by triggers other then the one you explicitly code in your sketch. Next the handleLedTimer is called from sleep() just before waking up the loop() at 1259ms and so on.

Some other helpful debugging methods

cprint(char *str); and cprintNum(char *str, uint32_t num);

cprint... methods let you print debug messages from within C (and C++) files to Serial. To use cprint() and cprintNum() to debug the C code files or .cpp files, add

#ifdef __cplusplus
extern "C"{
#endif // __cplusplus

void cprint(const char* str);
void cprintNum(const char* str, const uint32_t num);

#ifdef __cplusplus
} // extern "C"
#endif


to the top if the file and call them when you want to output debug msgs, Also in your main sketch (.ino), define cprint and cprintNum as

extern "C" void cprint(const char* str) {
  Serial.println(str);
}

extern "C" void cprintNum(const char* str, uint32_t num) {
  Serial.print(str);  Serial.print(' ');  Serial.println(num);
}


NOTE: Keep the debug strings short and don't try to print from inside a CRITICAL_REGION_ENTER(); CRITICAL_REGION_EXIT(); block of code.

uint16_t app_sched_queue_utilization_get(); and app_sched_queue_utilization_clear();

Your timer handlers, etc are executed in the same context as your sketch (instead of in an interrupt context). As they are triggered they are queued to be called from sleep(), when your loop() wakes up. This queue has a fixed length (default 20) which is defined in lp_timer_init.h If the triggers fire faster then your sketch can handle them, then the queue will fill up and you will loose some. You can check maximum queue usage by uncommenting
#define SCHEDULER_PROFILER
at the top of app_scheduler.h and then calling app_sched_queue_utilization_get(); in you sketch code.

You can also call app_sched_queue_utilization_clear(); to clear the maximum back to zero and start checking again.

See Low Power Button Debounce below for an example of using this.

A Low Power BLE UART

There a lots of BLE services defined by the BLE standard, but a replacement for the the “Classic Bluetooth” Serial Port Profile (SPP) is not one of them. This has meant manufactures have been left to define their own version of a BLE UART service. RFduno, RedbearLab, BLUNO, HM-10 and Nordic (maker of the nRF52 chips) all have their own unique UART service. pfodApp will connect to all of these services, but otherwise app support for these BLE UARTs is limited.

A commonly used service is Nordic's UART Service and that is the one that this library uses. Nordic provides a number of apps that will connect to that service, e.g. Nordic's nRF Toolbox and Nordic's nRF UART v2.0. (MicroBit's original implementation of Nordic's Service/Characteristics had the TX and RX swapped. pfodApp connects to that from as well.)

Here is the Blink_lp_BLE.ino code that lets you turn the blinking led on and off via BLE

#include <lp_BLESerial.h>
// Pin 13 has an LED connected on most Arduino boards, including NanoV2
int led = 13;
bool ledOn = false;
lp_timer ledTimer;
const unsigned long DELAY_TIME = 1000; // ms == 1sec
lp_BLESerial ble;

// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);
  ble.setName("Led Control"); // set advertised name, default name is "Nordic BLE UART"
  ble.begin(); // start advertising and be ready to accept connections
  ledTimer.startTimer(DELAY_TIME, handleLedTimer);
}

void loop() {
  sleep(); // just sleep here waiting for the timer to trigger
  // check for new BLE cmd  'a' starts blinking, 'b' stops blinking
  while (ble.available() ) {
    int i = ble.read();
    if ('a' == i) {
      ledTimer.startTimer(DELAY_TIME, handleLedTimer); // start blinking
    } else if ('b' == i) {
      ledTimer.stop();    // stop blinking
    }
  }
}

void handleLedTimer() {
  ledOn = !ledOn; // toggle state
  if (ledOn) {
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  } else {
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  }
}

When the sleep() is triggered to wake up, the loop() checks if there is a new command from the BLE connection and starts/stops the blinking. Note: the sleep() is triggered to wake up by multiple things, the timer, BLE connection, disconnection, receive data, etc, so you need to check if any data has been received.

The Nordic's nRF UART v2.0 app is used to test this connection. After connecting you can send a or b to start or stop the blinking.

With the blinking off, the multimeter reads, across the 1830R shunt, ~140mV advertising and ~130mV connected. The oscilloscope shows ~180mV advertising and ~160mV connected.
That is ~100uA advertising and ~90uA connected.

The supply current used by the BLE connection is controlled by the TX power, the advertising interval and the connection interval. The defaults used here are set in bleConstants.h and are TX power +4 (maximum available), advertising interval 500ms and connection interval 100ms min. to 150ms max. If you make the TX power lower using lp_BLESerial.setTxPower() (e.g. ble.setTxPower(-8); ), or make the advertising interval longer using lp_BLESerial setAdvertisingInterval() (e.g. ble.setAdvertisingInterval(1000); ) or make the connection intervals longer using lp_BLESerial.setConnectionInterval() (e.g. ble.setConnectionInterval(200,250); ), then the supply current needed will be less.

The lp_BLESerial also has options to specify connection and disconnection handlers and the size of the send buffer. The default send buffer size is 1024.

Sending data via lp_BLESerial

As well as receiving cmds, lp_BLESerial can also send data, but only 20 bytes at a time and only when the client, the Android app, checks for more data. If you try and send too much data too quickly it can get lost. lp_BLESerial includes a send buffer (default size 1024) that buffers the BLE writes and then releases them 20 bytes at a time at the maximum connection interval (default 150ms).

This examples sends the chip temperature once a sec when connected. The nRF52 chip has an on-board temperature sensor, accessed via getChipTemperature() and getRawChipTemperature(). It has a resolution of 0.25 deg C and is not particularly accurate, but if you calibrate it and apply the calibration factors in the sketch, then it could be used as a room temperature monitor. Historical temperatures could be stored and then sent when requested.

This example code, lp_BLE_temp.ino, does not do any calibration or storage of measurements. It just sends the temperature once a second when connected. This example also illustrates connection/disconnection handlers to start and stop timers, but they are not really needed as ble.print() will discard any bytes written while not connected.

#include <lp_BLESerial.h>          
lp_timer tempTimer;
const unsigned long DELAY_TIME = 1000; // ms == 1sec

lp_BLESerial ble;

// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  ble.setName("Chip Temperature"); // set advertised name, default name is "Nordic BLE UART"
  ble.setConnectedHandler(handleConnection); // when a connection is made
  ble.setDisconnectedHandler(handleDisconnection); // when the connection is dropped or goes out of range
  ble.begin(); // start advertising and be ready to accept connections
}

void loop() {
  sleep(); // just sleep here waiting a trigger
  while (ble.available()) {
    int i = ble.read(); // clear BLE recieve buffer and ignore
    // could add cmds there to send stored historical temps
  }
}

void handleTempTimer() {
  // send the current time and temp
  float temp = getChipTemperature();
  ble.print(millis());  ble.print(',');  ble.print(temp);  ble.println();
}

void handleConnection(BLECentral& central) {
  // could just start this in setup and leave running
  tempTimer.startTimer(DELAY_TIME, handleTempTimer);
}

void handleDisconnection(BLECentral& central) {
  // no real need to stop here, ble.print discards the bytes if not connected
  tempTimer.stop();
}

lp_comparator

In addition to the usual Arduino functions and the lp_BLESerial and getChipTemperature, this low power library has a low power pin voltage comparator. This provides low power triggers to wake up your sketch when the input voltage on a pin changes level.

This example, lp_BLE_comparator.ino, sets up a BLE Nordic UART and pin 2 as an input with a PULL_DOWN and then starts monitoring for voltage changes on pin 2 compared to a voltage level of ½ Vdd (i.e. 8/16 * Vdd)

#include <lp_BLESerial.h>
#include <lp_comparator.h>

// Pin 13 has an LED connected on most Arduino boards, including NanoV2
int led = 13;
int comparatorPin = 2; // D2/A2
lp_BLESerial ble;

void setup() {
  pinMode(comparatorPin, INPUT_PULLDOWN);  // set compare pin with INPUT_PULLUP or INPUT_PULLDOWN to prevent floating pin triggers
  // but the internal pullup/pulldown resistor is ~13K which draws an extra 254uA when INPUT_PULLUP is grounded or INPUT_PULLDOWN is connected to Vdd
  // for very low power use pinMode(2, INPUT) and supply a high value external pullup or pulldown resistor e.g. 100K draws ~33uA
  // ADVERTISING ~90uA, CONNECTED ~90uA when pin input grounded. i.e. LED off and no current through pull-down resistor
  pinMode(led, OUTPUT); // initialize the digital pin as an output.
  ble.setName("Pin Change"); // set advertised name, default name is "Nordic BLE UART"
  ble.begin(); // start advertising and be ready to accept connections
  lp_comparator_start(comparatorPin, REF_8_16Vdd, handlePinLevelChange); // always triggers pin LOW first, then if pin HIGH, will trigger HIGH
}

void loop() {
  sleep(); // just sleep here waiting a trigger
  while (ble.available()) {
    int i = ble.read(); // clear BLE recieve buffer and ignore
  }
}

// called when pin changes state, pinState is state detected,
// HIGH when Above and LOW when Below the reference voltage
void handlePinLevelChange(int pinState) {
  if (ble.isConnected()) {
    ble.print(millis()); ble.print(',');
    ble.print((pinState == HIGH) ? 'H' : 'L');  ble.println();
  }
  digitalWrite(led, pinState);   // turn the LED on when Above
}

When the input pin is open circuit, the internal pull down resistor keeps the pin voltage low and the led off. In this state the sketch uses less than 100uA both when advertising and when connected. However when the input is connected to a voltage > ½ Vdd, current flows through the pull down resistor (and the led turns on). The internal pull-down resistor is ~13K which draws an extra ~250uA, if the input in connected to Vdd. To avoid this excessive current, you should use pinMode(2, INPUT) and then provide your own external high value pull-up or pull-down resistor, say 100K (~33uA).

Using the Nordic UART app for testing, connect and then use a jumper lead to connect D2 to Vdd. You will see a lot of output scroll by. This due to the poor electrical connection as you push the jumper onto the pin. A lot of triggers happen in a short space of time and the ble.prints are buffered, up to 1024 bytes and then sent to your mobile at a slower rate.

The Low Power nRF52 library takes special care to ensure a flood of triggers from a noisy comparator input does not prevent other triggers from being processed. The lp_comparator triggers are placed on a separate queue to the timer and BLE triggers. The queue is only 4 slots deep, but the code ensures that the if the queue fills up then the last trigger is continually updated with the latest comparator result. This ensures that when your code has processed all the lp_comparator triggers, the last one processed is the current state of the pin. An artefact of this code is that your handler can receive two successive HIGH or two successive LOW triggers, when clearly the pin must have changed state in between.

lp_pinChange

The nRF52 can also trigger on input pin H/L transitions, but it can miss transitions when the chip is handling BLE functions, so lp_pinChange support has not been provide in this implementation. Instead use the lp_comparator with a ½ Vdd reference.

High Driver Output Modes

The nRF52 outputs set using the pinMode( .. , OUTPUT) have 'Standard' drive capability when driving low '0' and high '1' i.e. S0S1.
Standard drive can sink (output low) 2mA (typical) if the supply voltage is >1.7V (min 1mA if supply voltage is >1.7V)
Standard drive can source (output high) 2mA (typical) if the supply voltage is >1.7V (min 1mA if supply voltage is >1.7V)

The nRF52 also has two other output modes, High Drive (H0 and H1) and Disconnect (D0 and D1)

High Drive can sink (output low) 10mA (typical) if the supply voltage is >2.7V (min 3mA if supply voltage is >1.7V)
High Drive can source (output high) 9mA (typical) if the supply voltage is >2.7V (min 3mA if supply voltage is >1.7V)

The Disconnect options can be used to disconnect the pin from the output driver in either the low '0' or high '1' state. This is useful when driving busses that expect an open collector driver, like I2C

The following additional pinMode settings can be used to set High Drive and Disconnect for either low '0' or high '1' outputs (or both)

OUTPUT_S0S1 – Standard drive low and high. This is the same as pinMode OUTPUT
OUTPUT_H0S1 – High drive low, Standard drive high
OUTPUT_S0H1 – Standard drive low, High drive high
OUTPUT_H0H1 – High drive low, High drive high
OUTPUT_D0S1 – Disconnected when low, Standard drive high
OUTPUT_D0H1 – Disconnected when low, High drive high
OUTPUT_S0D1 – Standard drive low, Disconnected when high
OUTPUT_H0D1 – High drive low, Disconnected when high

nRF52 Chip Info

There are a number of different versions of the nRF52 chip. Each with there own set of bugs. Rev 4 of pfod_lp_nrf52 includes nrf52ChipInfo methods to display the chip version.

Example sketch

#include <nRFChipInfo.h>

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  for (int i=10; i>0; i--) {
    Serial.print(i); Serial.print(' ');
    delay(500);
  }
  Serial.println();
  Serial.print("Part: nRF");   Serial.println(nRF52PartNo(),HEX);
  Serial.print("Variant: ");  Serial.println((char*)nRF52Variant());
  Serial.print("Ram: "); Serial.print(nRF52RamKb()); Serial.println("Kb");
  Serial.print("Flash: "); Serial.print(nRF52FlashKb()); Serial.println("Kb");
  Serial.println("Setup finished");

}

void loop() {
  // nothing here
}


Example Output from the ChipInfo sketch for NanoV2

Part: nRF52832
 Variant: AAE1
Ram: 64Kb
Flash: 512Kb
Setup finished


How to decode the variant. (Note: some version of the AB variant return incorrect values Ram: 64K and Flash: 512K instead of the correct values, 32K, 256K)

prefix 
 Flash   Ram
AA      512     64
AB      256     32

suffix 
[A . . Z] Hardware version/revision identifier (incremental)
[0 . . 9]         Production device identifier (incremental)

Low Power Button Debounce

A common requirement is to monitor a push button input and ignore the button contact bounces. The sketch, lp_BLE_debounce.ino, illustrates how to write a low power button debounce.

#include <lp_BLESerial.h>
#include <lp_comparator.h>

// Pin 13 has an LED connected on most Arduino boards, including NanoV2
int led = 13;
int comparatorPin = 2; // D2/A2
lp_BLESerial ble;
lp_timer debounceTimer;
uint32_t debounceTimeOut = 20; // ms

int lastButtonState = -1; // not set initially
int buttonState = -1; // not set initially

void setup() {
  pinMode(comparatorPin, INPUT_PULLDOWN);  // set compare pin with INPUT_PULLUP or INPUT_PULLDOWN to prevent floating pin triggers
  pinMode(led, OUTPUT); // initialize the digital pin as an output.
  ble.setName("Button Debounce"); // set advertised name, default name is "Nordic BLE UART"
  ble.begin(); // start advertising and be ready to accept connections
  lp_comparator_start(comparatorPin, REF_8_16Vdd, handlePinLevelChange); // always triggers pin LOW first, then if pin HIGH, will trigger HIGH
}

void loop() {
  sleep(); // just sleep here waiting for a trigger
  while (ble.available()) {
    int i = ble.read(); // clear BLE recieve buffer and ignore
  }
}

// called when pin changes state, pinState is state detected, HIGH or LOW
void handlePinLevelChange(int pinState) {
  if (pinState != lastButtonState) {
    lastButtonState = pinState;
    debounceTimer.stop(); // stop last timer if any
    debounceTimer.startDelay(debounceTimeOut, handleDebounceTimeout);
  }
}

void handleDebounceTimeout() {
  buttonState = lastButtonState;   // input has settled
  // ble.print("maxQ:"); ble.print(app_sched_queue_utilization_get()); //needs #define SCHEDULER_PROFILER in utility/app_schedule.h
  ble.print(' '); ble.print((buttonState == HIGH) ? 'H' : 'L');
  digitalWrite(led, buttonState);   // turn the LED on when input HIGH
}


Each time the lp_comparator triggers, a delay timer is started. When it times out the push button contacts have settled. Note that the button state is initially -1. When the lp_comparator is started, lp_comparator_start, it always first fires a LOW trigger and then if the input is actually high it follows that with a HIGH trigger. This initializes the button state on startup.

app_sched_queue_utilization_get()

The timer triggers are placed on a separate queue from the lp_comparator (and BLE) triggers. As well as the time out trigger, each timer stop and start puts a trigger on the queue. The sketch just ignores these stop/start triggers, but they do take up space on the queue so you might be concerned that a noisy pin input would generate a lot of handlePinLevelChange inputs and so generate a lot of stop/start timer triggers and overload their queue. It turns out this does not happen in this low power library, because the lp_comparator trigger queue is limited to 4 slots and the if (pinState != lastButtonState) filters a lot of the noise.

However you may want to check that your sketch is not missing any triggers due to a full queue. app_sched_queue_utilization_get() lets you do this. To enable this check, in utility/app_schedule.h under arduino's package/sandeepmistry/hardware directory, un-comment the #define SCHEDULER_PROFILER line. You can then call app_sched_queue_utilization_get() to see the maximum number of slots that were used of the 8 configured in lp_timer_init.h Enabling this check shows that only 1 or 2 slots were ever used with a very noisy input. An actual push button is much cleaner.

Custom Low Power Control and Data Logging

You can use the free pfodDesigner Android app to create your own custom control menus/sub-menus and log and plot data and then have pfodDesigner generate the Low Power sketch for you. You will need to use pfodApp to connected and display the menu you created, but no Android programming is required. pfodApp handles all of that for you.

There are lots of tutorials on using pfodDesigner. Here we will create a button to pulse the Nano's led on for 2sec, and another menu item to display the voltage read on the A2 pin. A third button will open a chart of the A2 voltage readings, which are also saved to a log file on your mobile.

Install pfodDesignerV3 rev 3441+ from Google Play to your Android mobile. Start a new Menu and click on the Target button and then select Bluetooth Low Energy (BLE) and then Low Power nRF52 NanoV2 as the target. Use your mobile's back button to get back to the Menu Edit screen.

As you can see, pfodDesigner supports a lot of different BLE boards. For a low power sketch, choose the Low Power BLE Nano V2, not the RedBearLab one further down the list.

Then follow this tutorial to set D13 to pulse High for 2 secs.

Then add a Data Display menu item to display the value of the analogRead of A2, as described in this tutorial. Also, following that tutorial, add a Chart Button and set up a Plot to plot the A2 values. This also logs the readings to a log file on your mobile.

Finally, use the Generate Code button to generate the Arduino low power sketch for the NanoV2. Here is an example menu design, lp_BLE_NanoV2_example.ino It displays this menu when pfodApp is used to connect. This generated sketch still uses less than 100uA, both while waiting for a connection and while connected to your mobile and updating the menu/chart and logging the data.

You can also craft your own custom interactive graphical display. This tutorial, Custom Arduino Controls for Android, takes you through the steps. Very Low Power BLE, Part 2 – Temperature Humidity Monitor creates a custom interactive graphical display, in the Arduino sketch, to show the current temperature and humidity and plots of the historical values.

Conclusion

This tutorial has shown how you can easily create very low power BLE sketches in Arduino for the nRF52832 chip.

Supply currents of less than 100uA are easily achievable while keeping the chip active to make connections and send/receive data.

The free pfodDesigner lets you design menus/sub-menus, plot and log data and then generate the low power Arduino sketch for you. Connecting with pfodApp displays the menus and data while the nRF52 chip uses <100uA

No Android programming is required. pfodApp handles all of that.
No Arduino coding is required. The free pfodDesignerV2 generates complete low power sketches.

Very Low Power BLE, Part 2 – Temperature Humidity Monitor illustrates a practical application of this very low power programming and covers tuning for low supply current. The final Temperature Humidity Monitor uses ~25uA while connected and updating the mobile so you can run for years on a coin cell.

Roll Your Own BLE Service

In most case the general Nordic BLE UART service is all that you will need to collect data and control your device. However you can use the underlying BLEPeripheral class to define your own services and characteristics, but you will also need to code an Andriod / iOS app to recognize it. If you want to use one of the 40 or more 'standard' BLE services , for which apps are already available, you will need to code that service and characteristics and format the data, in your sketch. Covering these services is beyond the scope of this tutorial.



AndroidTM is a trademark of Google Inc. For use of the Arduino name see http://arduino.cc/en/Main/FAQ


The General Purpose Android/Arduino Control App.
pfodDevice™ and pfodApp™ are trade marks of Forward Computing and Control Pty. Ltd.




Forward home page link (image)

Contact Forward Computing and Control by
©Copyright 1996-2020 Forward Computing and Control Pty. Ltd. ACN 003 669 994