Easy Very Low Power BLE <20uA (2022/2024) With Arduino

by drmpf in Circuits > Arduino

7961 Views, 5 Favorites, 0 Comments

Easy Very Low Power BLE <20uA (2022/2024) With Arduino

bleModules2022.jpg
nRF53832_pinMapping.jpg

Update: 11th Nov 2024 – Rev 13 of pfod_lp_nrf52_2024.zip fixed A0 to A3 pin mapping for nRF52832_BARE_MODULE and added note about comparator pins and programming wiring

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: 10th May 2023 – Skylab SKB369 back in stock at <US$5 on Aliexpress

Update: 10th April 2023 – Added Raspberry Pi Pico DIY programmer

Update: 6th February 2023 – Rev 10 of pfod_lp_nrf25_2022 adds lp_ADC_calibrate() method to calibrate the ADC offset

Update: 6th 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: 20th February 2022 – added MuseLab programmer

Update: 12th 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

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

Programming Problems

For reliable programming it is best to solder the leads from the programmer to the board. While this tutorial shows jumper pins, it has been found that those connections become intermittent after repeated connects/disconnects which results in programming error messages. Soldering directly to the board overcomes those problems.

Background

This instructable completely replaces the previous Very Low Power BLE made Easy with Arduino – 2019 instructable. All the programmers and most of the BLE modules used in that 2019 tutorial are no longer available or exorbitantly expensive.

This instructiable uses Nordic Semiconductor nRF52832 modules and has been revised to handle the chip protection of the latest versions of the nRF52832 modules.

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. Other alternative nRF52832 modules will be covered as samples become available. 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. There is a XL52832-D01 from Aliexpress for ~US$5 with lots of I/O pins and Adafruit also has an in-expensive nrf52832 bare module, ~US$10, also with lots of I/O pins.

This tutorial is also available on-line at Easy Very Low Power BLE in Arduino -- Part 1 2022

Supplies

Parts List for MuseLab based Programmer / Current Monitor

Approximate cost as at Feb 2022, ~US$28 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 for the MuseLab module

OR Raspberry Pi Pico --~US$4 Adafruit 4864 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 100R 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

OR

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

Introduction

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

Part 1 – Building 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 specialized low power timers and comparators and debounced inputs and using pfodApp to connect to and control the nRF52 device.

Part 2 – A Very Low Power Temperature Monitor covers using a minimal nRF52 module to build a long life temperature monitor.

Part 3 - BLE Indoor/Outdoor Weather Station using very low power BLE sensors which run for years on a coin cell.

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

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

A minimal nRF52832 CMSIS-DAP program / serial debug module

A more extensive Programmer / Current Monitor 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

lp_pinChange

lp_ADC

High Drive Output Modes

nrf52ChipInfo

Low Power Button Debounce – lp_BLE_debounce_GT832E_01.ino

Custom Low Power Control and Data Logging

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 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 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 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 programmer No Longer Recommended

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.

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 – Not supported by pfod_lp_nrf52_2024

A Minimal NRF52832 CMSIS-DAP Program / Serial Debug Module

BasicProgrammer_sch.jpg

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 100R 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

CMSIS-DAP_ProgBrd.jpg
nRF52_MUSELAB_CMSIS_PRG.jpg

(See the parts list at the top of this Instructable)

In this circuit (pdf version), 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

pfod_lp_boards_2024.jpg

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 nRF52832. 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 is used. Once the support is installed via Arduino 1.8.19 it will be also available to Arduino V2.

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

1. Start the Arduino IDE 1.8.19

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". The latest version is 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.

5. Close 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. Delete the entire sandeepmistry directory.

5. Unzip pfod_lp_nrf52_2024.zip (Rev 11) 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 their own pre-configured programmer.

Here we are using the *Generic nRF52832 bare modules via DAPLink as the programmer.

Pin Mappings for Generic NRF52832 Boards

nRF53832_pinMapping.jpg

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. (Pin Mapping 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:- Serial.setPins(rx_pin,tx_pin);
SPI.setPins(miso_pin,clk_pin,mosi_pin);
Wire.setPins(sda_pin,scl_pin);

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

Pins near Radio Power Supply and Antenna Pins

The following pins P0.22. P0.23 .. through to P0.30 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, 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.

LFC (RTC) clock choices

There are two Generic nRF52832 boards to choose from. Generic nRF52832 (LFC crystal) and Generic nRF52832 (LFC RC osc)

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. 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 with gives better accuracy.

If your module has the extra external 32.768Hz crystal, choose the Generic nRF52832 (LFC crystal) otherwise choose Generic nRF52832 (LFC RC osc) The RC oscillator re-calibrates about once every 8sec and so uses a little more current ~1uA on average. If in doubt choose Generic nRF52832 (LFC RC osc) as it works on boards with or without an external crystal.

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

GT832E_01.jpg
nRF52_Programmer.jpg
CMSIS-DAP_programmer.jpg
flashLog.jpg
softDeviceFails.jpg

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.

Plug the USB programmer into the computer.

If the Arduino IDE programming (or load of softdevice 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 -- No necessary with pfod_lp_nrf52_2024

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


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

flashsoftdeviceOk.jpg

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)

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

ProgBrd_current.jpg

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 1K5 resistor leaving only the 330R 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 330R current shunt resistor is ~1.52V. The voltage measured across the 300R current shunt resistor is ~1.52V, So the supply current is

1.52 Volts / 30 ohms == 0.005 Amps (i.e. 5mA)

If the supply is much higher 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 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 (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.

Pin INPUT_PULLUP / INPUT_PULLDOWN

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.

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
Serial.begin(115200);
. . .

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
H
loop() woke up at 51ms
L
loop() woke up at 101ms
H
loop() woke up at 151ms
L
loop() woke up at 201ms
H
loop() woke up at 251ms
L
loop() woke up at 302ms
H

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<br>void cprint(const char* str);
void cprintNum(const char* str, const uint32_t num);<br>#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);
}<br>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 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;

void setup() {
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 nothing” most 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 odifications 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, 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_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. You can also call the lp_BLESerial isConnected() method (i.e. ble.isConnected(); ) to see if there is a current connection.

#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
tempTimer.stop();
}


Lp_comparator and Lp_ADC

lp_comparator_Screenshot.jpg

In addition to the usual Arduino functions and the lp_BLESerial and getChipTemperature, this low power library has a low power pin voltage comparator that can used on pins AIN0 (A0) to AIN7 (A7). That is pins P0.02, P0.03, P0.04, P0.05, P0.28, P0.29, P0.30 and P0.31. 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.

lp_pinChange

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.

lp_ADC

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.

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_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:
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

DebugOutput.jpg

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.

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

Target.png
SelectTarget.png
lp_RF52832Target.jpg
Selected_lp_nRF52.jpg
PO_pinSelection.jpg
Ex_menu.jpg
PlotA7.jpg

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 Protection

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

Conclusion

This instructable 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, Humidity, Air Pressure and Light Level 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 instructable.