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

Forward Logo (image)      

Easy Very Low Power BLE in Arduino -- Part 1

by Matthew Ford 23rd March 2024 (originally posted 14th December 2018)
© Forward Computing and Control Pty. Ltd. NSW Australia
All rights reserved.

Building Very Low Power BLE devices (<20uA)
made Easy with Arduino
No Android coding is required
Part 1 of 3 – 2022/2024

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

Update: 23rd March 2024 – Pi Pico prorgrammer no longer recommended. Use MuseLab MuseLab DAPLink instead.
Update: 10th March 2024 – pfod_lp_nrf52_2024.zip simplifies programming and handles protection on latest chip version
Update: 31st May 2023 – Rev 11 of pfod_lp_nrf52_2023.zip adds lp_comparator_isRunning() and changes lp_timer to finer ticks, 30.5us. Max 511sec, min 5 ticks =>152.5us
Update: 10
th May 2023 – Skylab SKB369 back in stock at <US$5 on Aliexpress
Update: 10
th April 2023 – Added Raspberry Pi Pico DIY programmer
Update: 6
th February 2023 – Rev 10 of pfod_lp_nrf25_2022 adds lp_ADC_calibrate() method to calibrate the ADC offset
Update: 6
th January 2023 – Rev 9 of pfod_lp_nrf25_2022 adds scanning support methods to BLEPeripheral and lp_ADC, revised Muselab programmer, added Help My Upload Failed
Update: 20
th February 2022 – added MuseLab programmer
Update: 12
th February 2022 – pfod_lp_nrf52_2022 Rev6 mods for 1/4 supply current (<20uA) and 4 times tx speed, corrected notes on Particle Debugger availability
Update: 4
th February 2022 – Completely revised to use currently available devices and programmer


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

Part 1Building Very Low Power BLE devices made Easy with Arduino, this one, covers building the CMSIS-DAP programmer and setting up Arduino to code nRF52 low power devices, the programming module and measuring the supply current. It also covers specialised low power timers and comparators and debounced inputs and using pfodApp to connect to and control the nRF52 device.

Part 2A Very Simple, Very Low Power Temperature Monitor covers using just a minimal nRF52 module and a coin cell to build a temperature monitor that runs for 5 years.

Part 3An Indoor / Outdoor Weather Station covers extending the very low power temperature sensor to add a sensor for temperature, relative humidity and barometric pressure and display the values on a Weather Station. The sensors run for ~4 ½ years

This tutorial is designed to allow the novice user to build very low power BLE devices, <20uA continuously while waiting for a connection and ~65uA 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 modules and has been revised to handle the chip protection of the latest versions of the nRF52832 modules. This tutorial 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 the (paid) 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.


Update 4th February 2022
This tutorial completely replaces the previous Very Low Power BLE made Easy with Arduino – 2019 tutorial. Most of the programmers and BLE modules used in that 2019 tutorial are no longer available or exorbitantly expensive.

This project was originally posted in December 2018 and used a BlackMagic programming module and a Redbear Programmer for programming the Redbear Nano V2 board. Later when the Redbear Programmer was no longer available, it was replaced with a Particle Debugger. Redbear Nano V2 is discontinued and the Particle Debugger and the BlackMagic programmer are out-of-stock. The Particle Debugger appears to still be a current item, just not available as at Feb 2022. Also the original project used a SkyLab Bluetooth Module SKB369 or a GT832E_01 as an alternative to the Redbear Nano V2. The SKB369 is still available but at a quoted price of US$190 each as at January 2022. Update: 10th May 2023 – Skylab SKB369 back in stock at <US$5 on Aliexpress.
This version pfod_lp_nrf52_2024 only supports programming via CMSIS-DAP programmers

The GT832E_01 (nRF52832 version), is used here as an example. It is still available at ~US$15 each, as at Jan 2022, but that price is also higher than previously.
The BLM_KTB522 available from via Aliexpress for ~US$6 each does not have an external low frequency 32Khz RTC crystal so should be programmed using the Generic nRF52832 (LFC RC osc) board setting.
The XL52832-D01 from Aliexpress for ~US$5 has lots of I/O pins (Jessinne on Aliexpress has Test Board Adaptor Plate, (breakout board) for this module). Search for nRF52832 on Aliexpress for other boards. Make sure the board is an nRF52832 not the cheaper nRF51822.
The Holyiot JY-16013-NRF52832 (~US$4.30 from Aliexpress) that has 6-8 I/O pins (+VDD/GND) on a 0.5” spacing that are accessible from the top and edge of the board. Holyiot also has nRF52832 'bare' modules in other board/pin configurations.
Adafruit also has an in-expensive nrf52832 bare module, ~US$10, also with lots of I/O pins on a fine spacing 0.7mm so a PCB is need for this module.
Other alternative nRF52832 modules will be covered as samples become available.

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.

Tutorial Outline

Boards and Programmers – MuseLab and Raspberry Pi Pico versions
Building the CMSIS-DAP programming module
Building the programming/test board
Installing the low power support for the nRF52832 in Arduino
Pin Mappings for Generic nRF52832 boards
Programming the GT832E_01 module
Help My Upload Failed
    Issue with NRF52 unexpectedly entering debug mode
How to Code for Low Power
Measuring the Supply Current – Blink_millisDelay.ino
A Low Power Timer – Blink_lp_timer_GT832E_01.ino
nRF52 Low Power Optimizations
Debugging Low Power
A Low Power BLE UART – lp_BLE_temp_GT832E_01.ino
Even Lower Power BLE, <20uA – LowerPower_Blink_GT832E_01.ino
Sending data via lp_BLESerial – lp_BLE_temp_GT832_01.ino
lp_comparator – lp_BLE_comparator_GT832E_01.ino
High Drive Output Modes
Low Power Button Debounce – lp_BLE_debounce_GT832E_01.ino
Custom Low Power Control and Data Logging
Removing nRF52 program protectionNot necessary with pfod_lp_nrf52_2024

Boards and Programmers

Although Sandeepmistry's original nRF5 Arduino add-on supports a number of nRF51/52 boards, this project only supports nRF52832 chips programmed via CMSIS-DAP programmers. To get the lowest power you need to use a board with just the nRF52832 chip since any extra devices onboard like accelerometers/leds will use more power. The pfod_lp_nrf52_2024.zip covers all nRF52832 bare modules, as well as the historical SKB369 and Redbear Nano V2 and other nRF53823 devices.

MuseLab CMSIS-DAP/ DAP-LINK / WiFi CMSIS-DAP modules – DAPLink used here

The Chinese company, MuseLab, produces inexpensive CMSIS-DAP (~US$5), DAPLink(~US%6) and CMSIS-DAP-Wifi modules. The DAPLink version is used here for programming on Windows 10 as it is more reliable. They are available from Aliexpress and also there appear to be a lot of copies available.

However also see below for a very simple DIY Raspberry Pi Pico CMSIS-DAP-V2 programmer. No Longer Recommended.

For Windows 7, mbedWinSerial_16466.exe (local copy here) recognises the DAP-LINK and CMSIS-DAP devices and installs the COM driver.

The DAPLink version adds a drag and drop DAPLink drive. That DAPLink drive is not used here. On Windows 10 it installs a drive letter that you can drag and drop to. On Windows 7, it needs this CMSIS_DAP_v2.inf file to install the unsigned driver for the DAPLink drive.

The third MuseLab option is the more expensive CMSIS-DAP-Wifi programmer, not recommeded, which connects via local wifi between the module connected to the computer and the target programmer. This CMSIS-DAP-Wifi works for programming but is not recommended because it buffers the debug serial output before sending it back to the computer and misses some of the target serial output data while it sends the buffered data. Tested with the Blink_lp_timer_debug_GT832E_01.ino from the Debugging Low Power section below. Also this module is not recognized by mbedWinSerial_16466.exe and so you have to use process described here to install the COM driver on Windows 7 (and other versions?)

Other Programming modules

Raspberry Pi Pico RP2040 CMSIS-DAP-V2 programmerNo Longer Recommended. Just stopped working. Use MuseLab MuseLab DAPLink instead

You can very easily build your own CMSIS-DAP-V2 programmer from a Raspberry Pi Pico board and four 100 ohm resistors as described here. Has been tested for removing program protection, programming via Arduino and Serial output.

STM32F103C8 based CMSIS-DAP programmer

You can also build your own CMSIS-DAP programmer from the ubiquitious STM32F103C8 (“BluePill”) as described here. This was the programmer originally used for this tutorial but is more complicated to build than the Raspberry Pi Pico based version above.

Other CMSIS-DAP programmers

There are many other CMSIS-DAP programmers available, such as this CMSIS-DAP Simulator for ~US$2.15 ,but note is has different pin outs. It has been tested for programming. Not yet tested for Serial.

Particle Debugger Programmer

As of Feb 2022, the Particle Debugger was out of stock, but still appears to be a current item. If you want to use the Particle Debugger to program your nrf52832, then refer to Easy Very Low Power BLE in Arduino (2019) -- Part 1 for the programmer construction details. but then continue here for installing the latest pfod_lp_nrf52_2024 Rev 11 board support.

ESP8266 BlackMagic Programmer – Tested for ProgrammingNot supported by pfod_lp_nrf52_2024

For programming there is also an ESP-01/ESP8266 based BlackMagic WiFi clone from https://github.com/walmis/blackmagic-esp8266 with pre-compiled binaries at https://github.com/J-Wrobel/blackmagic-espidf/tree/master/bins. The no_OTA version is all you need.

This programmer has a number of restrictions
1) openOCD does not connect for the removal of chip programming protection. https://github.com/walmis/openocd-blackmagic has a version of openOCD that includes a blackmagic interface (not tested)
2) It requires changes to the Arduino upload scripts, in platform.txt, to access an IP:port instead of the serial COM port.
3) The IP network is fix as (unless you edit and recompile the source) and you need to disconnect your computer from the internet to connect to this local network for programming. You may also need to set a static IP for your computer if you normal local network is not 192.168.x.x.

A minimal nRF52832 DAPLINK / CMSIS-DAP programmer / serial debug module

The DAPLink programmer is the most reliable (See Help My Upload Failed below). It is less sensitive to the size of the protection resistors in the SWCLK and SWDIO lines.

This minimal programmer has 100 ohm resistors in the SWCLK, SWDIO, RX and TX lines to protect against plugging in to the wrong pins. There is no protection on the 3v3 and Gnd lines. You just have to be careful and double check. Remember “one flash and its ash

The Raspberry Pi Pico programmer is similar with 100 ohm protection resistors added.

A more extensive Programmer / Current Monitor Board

(pdf version)

Parts List

Approximate cost as at Feb 2022, ~US$24 including USB extension cable (excluding nylon screws/nuts and shipping and an nRF52832 module)

MuseLab DAPLink module (click on the DAPLink option) – ~US$6 from Aliexpress
USB extension cable – ~US$2 Sparkfun CAB-13309
Raspberry Pi Pico – ~US$4 Adafruit 4864 ~US$4 or your favourite supplier

3 x 0.1uF 25V Ceramic capacitor e.g. Mouser FX18X7R1E104K ~ US$1.5
3 x 22uF 16V Ceramic capacitor e.g. Mouser FK20X7R1C226M ~ US$1.8
1 x 300R 1/4W 1% resistor e.g. Mouser MF0204FTE52-300R ~US$0.1
1 x 2K2 1/4W 1% resistor e.g. Mouser MFR-12FTE52-2K2 ~US$0.1
4 x 100 ohm 1/4W 1% resistor e.g. Mouser MFR-25FTE52-100R ~US$0.4
2 x 4 pin and 1 x 2pin female headers sockets e.g. Sparkfun PRT-11269 ~US$2 cut down the 8pin header
6 x 6 pin male header pins e.g. Sparkfun PRT-00116 ~US$1.5
female to female jumper e.g. Adafruit ID: 1951 ~US$2
male to male jumper e.g. Adafruit ID: 1956 ~US$2
Vero board (strip copper) e.g. Jaycar HP9540 ~AUD$5.5
plastic/cardboard sheet to insulate bottom of vero board e.g. cut out of plastic lid
and tape or nylon screws to hold it in place eg. 3mm x 12mm nylon screws, e.g. Jaycar HP0140 ~AUD$3 and 3mm x 12mm nylon tapped spacers, e.g. Jaycar HP0924 ~AUD$10

GT832E_01 nRF52832 BLE module ~ US$15 from Aliexpress
XL52832-D01 nRF52832 BLE module ~ US$5.5 from Aliexpress PLUS Jessinie Test Board Adaptor for nRF52832 ~ US$1.70

In this circuit, the 3 x 22uF are used to supply the chip's Tx current pulses. Otherwise the voltage drop across the current monitor resistors will cause the nRF52832 to shut down. The oscilloscope monitoring the current is connected between Target GND and Supply GND.

A small header board is used to connect the SWCLK and SWDIO leads and at the same time short out the Current Monitor Resistors so that programming, which draws more current, does not fail on low voltage. There is also another header for the Tx and Rx for the Arduino Serial Debugging connection. The SWCLK, SWDIO, Tx and Rx leads all have 100R resistors in series to protect against miss wiring to the nRF52832 pins. The 100R resistors limit the maximum current that flow via the in-chip I/O protection diodes to 33mA.

Regardless whether you use the 3V3 lead or another external supply to power the nRF52832, the negative (GND) return from the nRF52832 should be connected to the Target GND lead so that the return current will flow through the current monitoring resistors. If using and external supply, connect its -ve lead NOT to the nRF52832, but to the Supply GND lead.

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 an updated OpenOCD version and to only work with Nordic's nRF52832 via CMSIS-DAP. 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.19. Once the support is installed via Arduino 1.8.19 it will be available to Arduino V2 also

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

1. Start the Arduino IDE, V1.8.19
2. Go into File → Preferences
3. Add “
https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.jsonas an "Additional Board Manager URL"
4. Open the Boards Manager from the Tools -> Board menu and install "Nordic Semiconductor nRF5 Boards" by Sandeepmistry. Latest is version 0.8.0. This install is used to setup Arduino.

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 toolNot need for pfod_lp_nrf52_2024
(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 pfod_lp_nrf52 hardware support.

1. Download the pfod_lp_nrf52_2024.zip (Rev 11) 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.
4. Close the Arduino IDE
5. Delete the entire
sandeepmistry directory.
6. Unzip pfod_lp_nrf52_2024.zip to the
packages directory to install the pfod low power support. This will install the modified sandeepmistry directory.

That completes the Window 64bit installation. For other operating systems, you need to install the appropriate OpenOCD 0.11.0-1 binary.
1. From https://github.com/xpack-dev-tools/openocd-xpack/releases/tag/v0.11.0-1/ download the OpenOCD binary for your OS and hardware.
2. Open the
packages/sandeepmistry/tools/openocd/0.10.0-dev.nrf5 directory and delete the bin directory.
3. Extract the OpenOCD bin directory from the downloaded binary and place it in the
packages/sandeepmistry/tools/openocd/0.10.0-dev.nrf5 to replace the deleted directory.

Restart the Arduino IDE.
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 the pre-configured CMSIS-DAP programmer.
Here we are using the *Generic nRF52832 bare modules via DAPLink as the programmer.

Pin Mappings for Generic nRF52832 boards

There are many different 'bare' nRF52832 modules available and they expose different uC pins, and different numbers of pins, to the pcb edge connections. So to handle all of these possible configurations the *Generic nRF52832 boards just maps the nRF52832 uC pins to Arduino I/O, one to one. That is uC pin P0.00 is Arduino pin 0, P0.01 is pin 1 etc. You just need to use the pins you can access from the pcb connections. A0 to A7 are defined as the AIN pin numbers. D0 to D31 are not defined. Instead of D0 to D31 just use integers 0 to 31 for the digital I/O pin numbers.

(pdf version)

By default Serial Tx, Rx are pins 29 (P0.29) and 30 (P0.30). While Wire SDA and SCL are defaulted to pins 9 (P0.09) and 10 (P0.10) and SPI MOSI, MISO and SCK default to 6 (P0.06), 7 (P0.07) and 8 (P0.08). SPI SS is not defined and is not needed for SPI master mode. You can use any I/O pin to control the chip select (CS / SS) of the connected SPI device.

If these default pins settings are not convenient or not available on your module, you can override them with:-

Call these methods before call the respective begin() methods.

Pins near Radio Power Supply and Antenna Pins

For the following pins P0.22. P0.23 .. through to P0.30, Nordic recommends that thay should be limited to low drive (standard, see High Drive Output Modes below), low frequency (<10kHz) I/O only. That would limit Serial on P0.29/P0.30 to 9600 baud (and exclude I2C/Wire usage), but for testing/development 115200 works. Since the UART uses significant supply current, you will not normally have it enabled in the final device. However if you do use Serial, either use 9600baud or use Serial.setPins to use another pair of pins.

Generic Board choices – Generic nRF52832 (LFC crystal) or Generic nRF52832 (LFC RC osc)

There are two Generic nRF52832 boards to choose from. Generic nRF52832 (LFC crystal) and Generic nRF52832 (LFC RC osc) Some boards use the in-chip 32.768 kHz RC oscillator with an accuracy of about 1sec per hr at 25degC, while other boards have a 32.768Hz crystal which with gives better accuracy while using less current.

Start with the Generic nRF52832 (LFC RC osc) as it works for all boards, with or without an external crystal. Once you have that running you can try switching to Generic nRF52832 (LFC crystal), if your module does not have a crystal fitted the sketch will just hang in startup waiting for the crystal to start up.

The RC oscillator re-calibrates using the High Frequency (system) Clock, about once every 8sec and so uses a little more current ~2-3uA on average. The millis() and lp_timer uses the low frequency / low power clock, RTC, on the nRF52832. The timer accuracy depends on the accuracy of low frequency clock.

NFC Pins

In the *Generic nRF52832 boards, the NFC pins are re-assigned as GPIO pins for general I/O. NFC (Near Field Communication) support is not included in this package.

The pins dedicated to the NFC antenna function (P0.09/P0.10) have increase leakage current (typically 2uA, max 10uA) between the two pins when they are used in GPIO mode, and are driven to different logical values. To save power the two pins should always be set to the same logical value whenever entering one of the device power saving modes. The Wire Stop condition sets both these pins high so no excess current is drawn when Wire is not reading/writing. You can use different pins for the Wire interface by calling Wire.setPins(sda_pin,scl_pin); before calling Wire.begin();

Programming the GT832E_01 module

In this tutorial the GT832E_01 bare nRF52832 module is being used. This module has the advantage of 0.1” pcb pin spacing so you can attach headers to the pcb for easy connections. (GT832E_01 pins pdf version)

Connect the VDD 3.3V and Target GND leads and the SWCLK and SWDIO leads connected to the GT832E_01 module.
When the SWCLK/SWDIO header is plugged in the current monitor shunt resistors are shorted out to allow programming to succeed without causing low chip volts.

Help My Upload Failed

On Windows 10 (and 7), programming the nRF52832 can be a bit hit and miss.
NOTE: When using a CMSIS-DAP programmer, even though the COM port shows up, you may need to run the OpenOCD as described below each time you plug the programmer in, to force it to be recognised fully.
The DAPLink programmer does not seem to have this problem on Windows 10 and 7.

If the Upload of your Arduino sketch fails try these steps.
Check the wiring to the nRF52832. Wires on the correct pins and tight connections. If you get “Error: Error connecting DP: cannot read IDR” the SWD lead is not connected.
Plug the USB programmer into the computer.
If the Arduino IDE programming fails, try it again

If still fails

Open a cmd prompt, run as admininstrator, and cd to the OpenOCD dir
and and keep trying the cmd
bin\openocd -f interface/cmsis-dap.cfg -f target/nrf52.cfg -c "telnet_port pipe;tcl_port disabled;gdb_port disabled;log_output
until you get the line Info : Listening on port 3333 for gdb connections and the program is still running.
This can take 2 or 3 attempts
If using a CMSIS-DAP programmer and multiple attempts of this cmd fail, try using straight through connections without the 100 ohm protection resistors, checking the connections carefully. The DAPLink programming module is detected much more reliably.

The above cmd pipes the input from the current cmd terminal so you can just type the following command line to check the connection.

reset init

One user had problems with transferring from the programmer to battery operation. This does appears to be an atypical problem. However he solved this by powering the nRF52832 from the battery while programming. That is by only connecting the GND, SWCLK and SWDIO wires from the programmer. The 100 ohm series resistors in the SWCLK/SWDIO lines limit the current due to slight differences in the programmers 3V3 supply and the battery voltage.

Programming the Nordic Softdevice Not necessary with pfod_lp_nrf52_2024

With pfod_lp_nrf52_2024 the softdevice is automatically programmed each time you upload a sketch.

NOTE: Often you will need to remove the chip protection BEFORE 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.

Wire up the GT832E_01 module power and SWCLK/SWDIO as described above.
The nRF52 chip SWDIO line has an internal pull-up resistor and the SWDCLK line has an internal pull-down resistor.

Start the Arduino IDE and make sure CMSIS-DAP is the selected programmer (both CMSIS-DAP or the DAPLink programmers use CMSIS-DAP) and that the mbed COM port is selected as the Port, i.e. COM41

Then use the Tools → nRF52 Flash SoftDevice to install the softdevice

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.
Also see Help My Upload Failed above.

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 below

Otherwise if you get an error msg Error: Target not examined yet near the top of the Arduino programming output, check the wiring to your nRF52 module.

Power Supply Requirements

The VDD must rise quickly 0 to 1.7V is less than 60ms and be noise free.

The nRF52832 may not startup correctly if the rise time for the supply (VDD) from 0V to 1.7V is greater then 60ms, so if you are supplying the nRF52832 from a very low current source via a large capacitor you should add a supply monitor IC to keep the nRF52832 off until the supply voltage reaches 3V3 and the capacitor has sufficient charge to supply the ~20mA for 20ms while the chip starts up.
See Remote Controlled Light Switch
for an example.
Also from the chip spec, “A step increase in supply voltage of 300 mV or more, with rise time of 300 ms or less, within the valid supply range, may result in a system reset”. A clean power cycle should exit debug mode.

Issue with NRF52 unexpectedly entering debug mode

This note refers to the nRF51 and another comment suggests this problem is fixed in the nRF52, but if you are having problems try grounding the SWCLK pin after programming. The recommended 1K resistor between there and the programmer, see the Programmer Circuit above, will protect the programmer.

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 pin
OR 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.

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.
Also see
Optimizing Power on nRF52 Designs (local copy here)

NOTE: If your supply current is not as low as you would like/expect, check that
a) the SWD/SCLK cables have been removed
b) the board has been powered cycled
c) Serial.begin() has NOT been called
d) the TX/RX serial cables are removed

Basic Blink Sketch

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 following nRF52832_basic_blink.ino example that blinks the led pin at 10Hz. Note: The Arduino 1.8.19 Blink example will not compile as there is no LED_BUILTIN on bare nRF52832 modules.

int led = 14; // the pin, P0.14, an external led is connected to

// 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(50);               // wait for a 50ms
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(50);               // wait for a 50ms

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

Measuring the Supply Current

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

To measure the supply current

1) Upload the nRF52832_basic_blink.ino sketch onto the GT832E_01, via the SWCLK/SWDIO header. With this header plugged in the current monitoring resistors are shorted out allow programming to succeed. Do not connect the Tx/Rx leads to the GT832E_01 target.
2) Remove the USB 5V supply supplying the GT832E_01 via the 3v3 regulator to reset the nRF52832 after the programming is finished. This is necessary in order to measure the correct supply current.
3) Move the current shunt shorting link so that it shorts out the 2K2 resistor leaving only the 300R resistor in the ground line. For very low current measurements just remove the link completely
4) Remove SWCLK/SWDIO header.
Important: Leave this header inserted when power cycling the nRF52 after programming to ensure it starts up correctly. The nRF52 draws ~20mA for ~20ms on startup. Important: Remove this SWCLK/SWDIO header after the nRF52 starts up as it draws ~100uA. Also remove the Serial Tx/Rx header if connected.

Connection your multimeter, set to Hz, to pins 14 and GND should read 10Hz

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

For the nRF52832_basic_blink.ino, sketch the supply current is about 5mA (with no led connected between P0.14 and GND). The voltage measured across the 300R current shunt resistor is ~1.53V. The voltage measured across the 300R current shunt resistor is ~1.52V, So the supply current is
1.53 Volts / 300 ohms == 0.005 Amps (i.e. 5mA)

If the supply current is much higher than this, there not enough voltage left to power the chip. This sets the upper limit on the supply current that can be measured with a 300R shunt resistor.

NOTE: If the oscilloscope / 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.

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 while running your uC as fast as it can.

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. However while millisDelay keeps the loop() running as fast as it can, the uC is still continually running using lots of current. For low power you need to use a low power timer instead.

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 nRF52832_basic_blink.ino, above, most of the time the uC is just stuck in a tight loop waiting for the delay() to expire. 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_GT832E_01.ino

#include <lp_timer.h>
int led = 14; // Use P0.14 has the 'led' pin
bool ledOn = false;
lp_timer ledTimer;
const unsigned long DELAY_TIME = 50; // ms == 10Hz

void setup() {
  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 rest 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 2K2 + 300R == 2K5, i.e. with the shunt link completely removed, gives meter readings of about 22mV, which implies an average supply current of 9uA (0.022V / 2500 ohms) = 0.009mA

So using sleep() and the lp_timer() has reduce the supply current from ~6mA to ~0.009mA (~9uA, with no led attached)

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.

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.
In pfod_lp_nrf52_2023, Rev 11, the nRF52's RTC counter is configured to count 30.5us increments. The maximum timeout is now 511secs and the minimum is 5 ticks =>152.5us.
startDelay() and startTimer() continue to take ms arguments but scale them by 1000 and call the new startDelay_us() and startTimer_us() methods. Call startDelay_us() and startTimer_us() directly, in preference, to avoid the times 1000 multiplication. To revert to the previous tick size comment out the #define LP_TIMER_US line the utility/lp_timer_speed.h file.

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 30.5us. That is calls to micros() will increment in multiples of 30.5us.

nRF52 Low Power Optimizations

You will see in the on-line nRF52 forums references to enabling the nRF52 DC to DC converter and enabling/disabling peripherials. See Optimizing Power on nRF52 Designs (local copy here) The DC to DC converter option requires extra components, which boards like the NanoV2 has but, which 'bare' nRF52 boards, like GT832E_01 and SkyLab, will not, so these sketches don't use it.

As for the peripherials such as Serial (UART), SPI and Wire. These are disabled until you call .begin() and then disabled again when you call .end(). So for lowest power usage call .end() for SPI and Wire when you are not using them.

Extra Components Use Extra Power

Extra components, leds, sensors (accelerometers), etc, use power and when the basic chip with BLE is only using 74uA, 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 GT832E_01 has no extra external components. You need to provide a 3.3V supply. The MAX8881 3.3V regulator used on this programming/test board uses typically 3.5uA and its supply current is included in the measurements.


The internal pull-up/pull-down resistors are extra components that used extra power. They are typically 13K ohms (11K to 16K). These need to be taken into account when calculating the supply current drawn by the external circuit connected to this pin. Grounding a pin with a pullup resistor ( INPUT_PULLUP) draws an additional 250uA. Similarly for connecting a pin, with a pulldown resistor (INPUT_PULLDOWN), to VDD. To minimize the extra current, use larger external resistors instead of INPUT_PULLUP or INPUT_PULLDOWN.

However unconnected inputs should be set to INPUT-PULLUP so the input does not float and wake up the chip (see this link). Setting INPUT-PULLUP is only necessary if your code changes a pin to an input and there is not an external pullup resistor attached.

The internal pull-up/pull-down resistors, if used, together with the external circuit components, also needs to be taken into account when calculating the voltage applied to a comparator pin input.

Debugging Low Power

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.

The Generic nRF52832 board defaults to P0.29 and P0.30 as the Serial Tx, Rx , but you can override that in your sketch using Serial.setPins(rx_pin,tx_pin); before calling Serial.begin();

void setup() {
  Serial.setPins(11,12); // remap Rx to P0.11 and Tx to P0.12
 . . . 

The Blink_lp_timer_debug_GT832E_01.ino sketch does that to remap Serial Tx to P0.12 and Serial Rx to P0.11
Plug in the Tx/Rx header and connecting “to Target Tx” lead to P0.12 and the “to Target Rx” lead to P0.11 lets you display the Serial output on your computer

There is some sample output from Blink_lp_timer_debug_GT832E_01.ino

startup() completed at 1ms
loop() woke up at 4ms
loop() woke up at 51ms
loop() woke up at 101ms
loop() woke up at 151ms
loop() woke up at 201ms
loop() woke up at 251ms
loop() woke up at 302ms

It is important to remember that the loop() is woken up by triggers other then the one you explicitly code in your sketch. The handleLedTimer is called from sleep() just before waking up the loop().

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"

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) {

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
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 other apps' 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_GT832E_01.ino code that lets you turn the blinking led on and off via BLE

#include <lp_BLESerial.h>
#include <lp_timer.h>
int led = 14; // Use P0.14 has the 'led' pin
bool ledOn = false;
lp_timer ledTimer;
const unsigned long DELAY_TIME = 50; // ms = 10Hz
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/BLE 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
      digitalWrite(led, LOW); // turn off

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 oscilloscope reads, across the 2K5 shunt, ~163mV(avg) advertising and ~148mV(avg) connected. No Led connected.
That is ~65uA advertising and ~59uA connected. Important: Remove the SWCLK/SWDIO header after the nRF52 starts up as it draws an additional ~100uA

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. However just turning off the advertising is a much more effective means of reducing the supply current.

Even Lower Power BLE, <20uA

As mentioned above the trick to very low power BLE is to do nothing most of the time. In most use cases the BLE device spends most of its time waiting for a connection and then spends a relative short time sending the data and then disconnects and goes back to waiting. In the example above, Blink_lp_BLE_GT832E_01.ino, the device is advertising while ever it is not connected. This is the default. However this means the device is using ~71uA most if the time. This current can be dramatically reduced by turning off advertising and “doing nothingmost of the time. Rev 6 of the pfod_lp_nrf52_2002 code adds methods to set the advertising timeout, and start/stop the advertising.

The two new methods are :-
void setAdvertisingTimeout(uint16_t sec); which sets how long the advertising will run after it is started and
void setAdvertising(bool on); which turn adverting on true or off false.

The modifications to Blink_lp_BLE_GT832E_01.ino are contained in LowerPower_Blink_GT832E_01.ino They are simple.

lp_timer BLE_AdvertisingRestartTimer; // the advert restart timer
const unsigned long BLE_ADVERTISING_ms = 20 * 1000;// restart advertising every 20sec
const uint16_t BLE_ADVERTISING_TIMEOUT_secs = 2;// run advertising for 2 seconds
// pfodApp needs at least 2sec of advertising to scan and connect

void restartAdvertising() {  // called by timer every 20seconds
  ble.setAdvertising(true); // ignored if currently connected

void setup() {
  pinMode(led, OUTPUT);
  ble.setName("Led Control"); // set advertised name, default name is "Nordic BLE UART"
  ble.setAdvertisingTimeout(BLE_ADVERTISING_TIMEOUT_secs); // 2 sec
  BLE_AdvertisingRestartTimer.startTimer(BLE_ADVERTISING_ms, restartAdvertising); // restart advertising every 20sec for 2sec
  ble.begin(); // start advertising and be ready to accept connections
  ledTimer.startTimer(DELAY_TIME, handleLedTimer);

In setup() set the advertising timeout to 2 seconds before calling lp_BLESerial.begin() ( ble.begin(); ) and start a timer to trigger every 20seconds to restart the advertising for another 2 seconds. With these modifications, when not connected, for 2sec, while advertising the supply current is ~65uA. While for 18secs when not advertising it is about ~12uA for an average of ~17uA.

The downside is that most applications, like Nordic's nRF UART v2.0 and pfodApp, need to scan the device's advertising prior to connecting, so it can take upto 18 seconds before the device is scanned and can be connected. If the advertising on for less than 2 seconds then pfodApp does not reliably detect the device. If the interval between advertising restarts is greater than about 30 seconds most apps will have terminated their scanning procedure, so 2 seconds on in a 20 second cycle seems close to ideal.

Sending data via lp_BLESerial

As well as receiving cmds, lp_BLESerial can also send data, but only 20 bytes in each packet and up to 4 packets per connection interval. If you try and send too much data too quickly it can get lost.

pfod_lp_nrf52_2022 Rev 6 changes the way the BLE UART works. In Rev 6 the tx speed is increased by a factor of 4 and the lp_BLESerial print statements will now block if the Tx buffer is full. Previously excess chars were just dropped. You should avoid sending data so fast that the print statements block as this will interfere with the running of the rest of the code as is also the case in normal Arduino sketches. Your code can check is there is sufficient space in the Tx buffer by calling
size_t lp_BLESerial.availableForWrite();
The suggested way to do this is in a timer method. If not enough space is available then skip the write and check again next time the timer fires.

lp_BLESerial buffers the BLE writes and then releases them 4 x 20 bytes at a time at connection interval (default in the range 100ms to 150ms). The lp_BLESerial constructor allows you to specify the size of the Tx buffer. The default is size 1024 bytes. If the device is not connected then all print/write output is discarded. The Tx buffer is also cleared when the device disconnects.

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 (+/- 2.5 deg C between -40C to +85C) , 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. The handleTempTimer() only runs once per sec and so will not overfill the Tx buffer and block.

This example code, lp_BLE_temp_GT832_01.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 and the Tx buffer is cleared on disconnection.

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

void setup() {
  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 for 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


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_GT832E_01.ino, sets up a BLE Nordic UART and pin 2 as an input with a INPUT_PULLDOWN and then starts monitoring for voltage changes on pin 2 compared to a voltage level of ½ Vdd (i.e. 8/16 * Vdd)

Note: The internal pull-up/pull-down resistors are typically 13K ohms (11K to 16K) These need to be taken into account when calculating the voltage applied to the comparator and the current drawn by the external circuit connected to this pin.

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

int led = 14; // Use P0.14 has the 'led' pin
bool ledOn = false;
int comparatorPin = A7; // P0.31
lp_BLESerial ble;

void setup() {
  pinMode(comparatorPin, INPUT_PULLUP);  // 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
  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 > 0V, current flows through the pull down resistor. 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 leave the pinMode at its default startup setting ( pinMode(2, INPUT) ) and then provide your own external high value pull-up or pull-down resistor, say 100K (~33uA). The downside of this approach is that the input is now more susceptible to noise pickup.

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 nRF52832 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.

You can have lp_comparator and lp_ADC running at the same time as long as they are monitoring different ADC pins. Calling lp_comparator_start( ) while it is already running will return a non-zero error code. Use lp_comparator_isRunning() (introduced in Rev 11) to check if the lp_compartor is running.


The nRF52832 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.


Rev 9 of the pfod_lp_nrf52, adds a non-blocking version of ADC (an analogRead replacement). The standard blocking analogRead(..) method is still available and the standard configuration methods analogReadResolution(..) and analogReference(..) also configure lp_ADC. The default ADC sample time has been increased to 10uS to accommodate the higher source resistances found on low power circuits. The 10uS sample time is suitable for source resistances upto 100K, e.g a 200K / 200K input voltage divider.

See the example sketch lp_ADC_test.ino

Calling lp_ADC_start( ) starts the conversion and calls back the handler with the count as an argument when the conversion completes.

  lp_ADC_calibrate(); // if you want the offset calibrated before taking this sample.
  uint32_t err = lp_ADC_start(28, handle_ADC_result);  // pin 28 on BareBoard nRF52832 == A4

// this callback method is called on the loop() thread
void handle_ADC_result(int count) {
  Serial.print("ADC result woke loop ");
  Serial.print((count * 3.0) / 1023);
  Serial.print("V  -- ");

The callback method is called on the loop() thread so no synchronisation or volatile variables are required. You can call lp_ADC_start( ) again directly from the handler if required.
The lp_ADC_calibrate() method can be called from the loop() at any time and sets a flag to perform an single offset calibration for the ADC before the next sample is taken.
The calibration flag is cleared by the lp_ADC_start( ). If lp_ADC_calibrate() can be called immediately after calling lp_ADC_start( ), the calibration is done before the next sample after the current sample returns. Normal samples take about 40us (non-blocking). Samples with calibration take about 140us (non-blocking)

The lp_comparator and the lp_ADC can be used at the same time provided they are on different analog pins.

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 nRF52832 chip. Each with there own set of bugs. Rev 5 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:
  for (int i=10; i>0; i--) {
    Serial.print(i); Serial.print(' ');
  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)

 Flash   Ram
AA      512     64
AB      256     32

[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_GT832E_01.ino, illustrates how to write a low power button debounce.

#include <lp_BLESerial.h>
#include <lp_comparator.h>
#include <lp_timer.h>
int led = 14; // Use P0.14 has the 'led' pin
bool ledOn = false;
int comparatorPin = A7; // P0.31
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_PULLUP);  // 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.


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 GT832E_01's led on for 2sec, and another menu item to display the voltage read on the A7 pin (P0.31). A third button will open a chart of the A7 voltage readings, which are also saved to a log file on your mobile.

Install pfodDesignerV3 rev 3.0.3875+ 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 Generic nRF52832 bare modules as the target. Use your mobile's back button to get back to the Menu Edit screen.

Once the Generic nRF52832 bare modules is selected, when you go to connect an digital I/O pin to a menu item, pfodDesignerV3 displays a list of the P0... pins.

Choose a pin that is exposed by your module. Here pin P0.14 is selected to drive the external led output.

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

Then add a Data Display menu item to display the value of the analogRead of A7, as described in this tutorial. Also, following that tutorial, add a Chart Button and set up a Plot to plot the A7 values. This also logs the readings to a log file on your mobile.
Note: The default ADC scale is 0 to 3.0V using the internal reference of 0.6 and a gain of 5 == 3.0V full scale and the resolution is 10 bits ie. 0 to 1024
So set Edit Display Max to 3.0 when setting up the analogRead and plot of A7

Finally, use the Generate Code button to generate the Arduino low power sketch for the nRF52832 module. Here is an example menu design, lp_BLE_GT832E_01_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.

Removing nRF52 program protectionNot necessary with pfod_lp_nrf52_2024

With pfod_lp_nrf52_2024 the nRF52 is completely erased each time it is programmed. This removes the program protection and allows the softdevice and sketch to be uploaded.

If the softdevice flash works, then that is OK, but often modules will have been protected against re-programming and you will get this error output in the Arduino window

Open On-Chip Debugger 0.10.0-dev-00254-g696fc0a (2016-04-10-10:13)
Licensed under GNU GPL v2
For bug reports, read
debug_level: 2
Info : only one transport option; autoselect 'swd'
adapter speed: 10000 kHz
cortex_m reset_config sysresetreq
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: FW Version = 1.10
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : reduce speed request: 10000kHz to 5000kHz maximum
Info : clock speed 10000 kHz
Info : SWD IDCODE 0x2ba01477
Error: Could not find MEM-AP to control the core
Error: Target not examined yet
Error while flashing SoftDevice.

From Nordic Semi – Debug and Trace page
CTRL-AP - Control Access Port. The Control Access Port (CTRL-AP) is a custom access port that enables control of the device even if the other access ports in the DAP are being disabled by the access port protection. Access port protection blocks the debugger from read and write access to all CPU registers and memory-mapped addresses.
Disable access port protection. Access port protection can only be disabled by issuing an ERASEALL command via CTRL-AP. This command will erase the Flash, UICR, and RAM.

In that case you need to set the ERASEALL command register in the nRF52 to clear the memory and make the device programmable again. The version of openOCD supplied with sandeepmistry nRF52 does not include the apreg command needed to write to the ERASEALL command register so you need to install a later version.

These OpenOCD versions worked on Window 7
https://github.com/ilg-archived/openocd/releases/tag/gae-0.10.0-20170124 has windows/mac and linux compiled versions.
The windows 64bit version https://github.com/ilg-archived/openocd/releases/download/gae-0.10.0-20170124/gnuarmeclipse-openocd-win64-0.10.0-201701241841-setup.exe worked.
As did https://github.com/ilg-archived/openocd/releases/download/v0.10.0-12-20190422/gnu-mcu-eclipse-openocd-0.10.0-12-20190422-2015-win64.zip

(These Windows pre-compiled versions DID NOT WORK on Windows 7, http://gnutoolchains.com/arm-eabi/openocd/ )

Open a command prompt as Administrator and change dir to the OpenOCD install directory and enter the command
(To open cmd as Administrator, type cmd into the windows program search (bottom left) and then right click on cmd.exd listing and choose run as Administrator.)

bin\openocd.exe -d2 -f interface/cmsis-dap.cfg -f target/nrf52.cfg
You may need to allow access to your local network

The response is

Open On-Chip Debugger 0.10.0 (2018-11-30) [https://github.com/sysprogs/openocd]
Licensed under GNU GPL v2
For bug reports, read
debug_level: 2
Info : auto-selecting first available session transport "swd". To override use '
transport select <transport>'.
adapter speed: 1000 kHz
cortex_m reset_config sysresetreq
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 1.10
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 1000 kHz
Info : SWD DPIDR 0x2ba01477
Error: Could not find MEM-AP to control the core
Info : Listening on port 3333 for gdb connections

Then open a terminal window e.g. TeraTerm (Windows) or CoolTerm (Mac) and connect to port 4444

The telnet window will show a > and the command prompt will show
Info : accepting 'telnet' connection on tcp/4444

In the telnet window (i.e. TeraTerm) type
nrf52.dap apreg 1 0x04
this returns 0x00000000 showing the chip is protected. Then type
nrf52.dap apreg 1 0x04 0x01
and then
nrf52.dap apreg 1 0x04
this returns 0x00000001 showing the chip is now set to ERASEALL on next restart.

Close the telnet connection and also use Ctrl-C to exit the openOCD program in the command prompt and then power cycle the nRF52 module and it will be now ready to program.

Now retry flashing the softdevice.


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 20uA 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 <20uA while not connected

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

The next part in this series will be A Very Low Power Temperature Monitor (under construction)

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-2024 Forward Computing and Control Pty. Ltd. ACN 003 669 994