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

Forward Logo (image)      

Easy Very Low Power BLE in Arduino -- Part 1
2022

by Matthew Ford 30th April 2022 (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 2 – 2022

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

Update: 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
Update: 4th February 2022 – Completely revised to use currently available devices and programmer

Introduction

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

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

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.

Background

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.

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.

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

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
How to Code for Low Power
Programming the GT832E_01 module
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
High Drive Output Modes
nrf52ChipInfo
Low Power Button Debounce – lp_BLE_debounce_GT832E_01.ino
Custom Low Power Control and Data Logging
Removing nRF52 program protection

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

The Chinese company, MuseLab, produces inexpensive CMSIS-DAP (~US$9), DAP-LINK and CMSIS-DAP-Wifi modules. They are available from Aliexpress and also there appear to be a lot of copies available. The CMSIS-DAP is used here for programming. The mbedWinSerial_16466.exe (local copy here) recognises the CMSIS-DAP (and DAP-LINK) device and installs the COM driver.

The slightly more expensive DAP-LINK version can also be used. It adds a drag and drop DAP-LINK drive. That DAP-LINK drive is not used here and for Windows 7, it needs this CMSIS_DAP_v2.inf file to install the unsigned driver for the DAP-LINK 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

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.

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_2022 Rev 2 board support.

ESP8266 BlackMagic Programmer – Tested for Programming

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 192.168.4.1 (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 CMSIS-DAP program / serial debug module

This minimal programmer has 1K 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

A more extensive Programmer / Current Monitor Board


(pdf version)

Parts List

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

MuseLab CMSIS-DAP module – ~US$9 from Aliexpress
USB extension cable – ~US$2 Sparkfun CAB-13309

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 1K 1/4W 1% resistor e.g. Mouser CMF1/41001FLFTR ~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

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 1K resistors in series to protect against miss wiring to the nRF52832 pins. The 1K resistors limit the maximum current that flow via the in-chip I/O protection diodes to 3.3mA.

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 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 on a Windows 7 PC.

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

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

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

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

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

Install the pfod_lp_nrf52 hardware support.

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

Note: the boards with * against them do not use the selected Programmer. Instead they use their own pre-configured programmer.

Here we are using the *Generic nRF52832 bare modules via CMSIS-DAP 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:-
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), low frequency I/O only.

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.

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

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.

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)

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 ~100nA. 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.

Issue with NRF52 unexpectedly entering debug mode

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

A 1nF (no resistor) between SWCLK and GND was tested and with that in place programming of the chip failed, but the chip continued to run and could be connected to. So do not add the suppression capacitor/resistor until after you have finished programming.

The final suggested solution is to connect a wire from SWCLK to GND, to ground it, after programming.

Issue with NRF52 not starting up correctly with a large supply capacitor and limited supply current

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.

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.

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

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

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.

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

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

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


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

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

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

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

uint16_t app_sched_queue_utilization_get(); and app_sched_queue_utilization_clear();

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

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

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

A Low Power BLE UART

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

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

lp_comparator

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

This example, lp_BLE_comparator_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.

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.

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

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

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

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
        http://openocd.org/doc/doxygen/bugs.html
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 and change dir to the OpenOCD install directory and enter the command

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
        http://openocd.org/doc/doxygen/bugs.html
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 127.0.0.1 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.

Conclusion

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

Supply currents of less than 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-2020 Forward Computing and Control Pty. Ltd. ACN 003 669 994