Very Low Power BLE Made Easy With Arduino

1,832

13

2

Building Very Low Power BLE devices made Easy with Arduino. No Android coding is required
Novice users can build BLE devices that can run continuously for over a year on 2 x AAA batteries or a small LiPo.

Update: 6th January 2018 – Replaced BlackMagic Probe with less expensive, and less robust, Particle Debugger for programming

Introduction

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

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

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

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

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

Step 1: Quick Start

  1. Wire up the programmer, -- Step 2
  2. Install the low power support -- Steps 3 and 4
  3. 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. -- Step 13

Boards and Programmers

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

The test device is a RedBear Nano V2. This is small enough to be used for real devices. The tutorial will be updated later to also cover using stripped down modules like the RedBear BLE Module or the SkyLab Bluetooth Module SKB369 Although the Nano V2 board includes a low power regulator, an external regulator is used so that the bare chip boards can be programmed also. This external regulator is also needed to ensure start up from very low current sources.

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

Instructable Outline

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

Step 2: Building the Programming / Testing Board

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

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

Parts List

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

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

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

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

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

To protect both the device being programmed and the Particle Debugger from differing supply voltages, for example when one is powered and the other is not, 1K resistors are placed in series with the SWCLK, SWDIO, TX and RD leads. These limit the maximum current that can flow in-chip the I/O protection diodes.

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

Step 3: Installing the Low Power Support for the NRF52832 in Arduino

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

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

Download and Install the Arduino IDE

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

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

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

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

Install the Particle Debugger driver

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

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

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

Install the pfod_lp_nrf52 hardware support.

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

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

Here we are using the BLENano2 via Selected Programmer board, and selecting CMSIS-DAP as the programmer.

Step 4: Connecting the Particle Debugger to the NRF52832 Chip

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

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

Programming the Nordic Softdevice

Before you can run a sketch you
need to first load a 'softdevice' into the nRF52832 chip. The project uses the s132 softdevice, s132_nrf52_2.0.1_softdevice.hex (http://www.nordicsemi.com/eng/content/download/95151/1606944/file/s132_nrf52_2.0.0.zip)

The pfod_lp_nrf52.zip already includes s132, so you don't need to download it from the Nordic website. However you still need to program it into your nRF53832 chip.

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

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

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

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

Step 5: How to Code for Low Power

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

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

<p>int led = 13;</p><p>// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
}</p><p>// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second
}</p>

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

Delays are evil. Use timers instead.

Looking inside the delay() method you will see

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

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

Don't use delay()

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

Rewriting Blink.ino using millisDelay gives Blink_millisDelay.ino

<p>#include <br>// Pin 13 has an LED connected on most Arduino boards, including NanoV2
int led = 13;
bool ledOn = false;
millisDelay ledDelay;
const unsigned long DELAY_TIME = 1000; // mS == 1sec</p><p>// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);
  ledDelay.start(DELAY_TIME);
}</p><p>// the loop routine runs over and over again forever:
void loop() {
  // do other stuff here
  if (ledDelay.isFinished()) {
    ledDelay.repeat(); // start a repeat the delay
    ledOn = !ledOn; // toggle state
    if (ledOn) {
      digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    } else {
      digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    }
  }
}</p>

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

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

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

Step 6: Measuring the Supply Current

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

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

For the Blink_millisDelay.ino, sketch the supply current is about 6mA (The led on the NanoV2 draws a fraction of a mA).

The voltage measured across the 330R current shunt resistor is ~2V, So the supply current is 2Volts / 330 ohms == 0.006 Amps (i.e. 6mA)

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

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

Issue with NRF51 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 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, either with Particle Debugger or RedBear's DAP V1.5 programmer. Re-flashing the Softdevice worked and made the chip programmable again. So do not add the suppression capacitor/resistor until after you have finished programming. My final solution is to connect a wire from SWCLK to GND, to ground it, after programming.

nRF52 Low Power Optimizations

You will see in the on-line nRF52 forums references to a) enabling the nRF52 DC to DC converter and b) disabling the Serial UART, to reduce supply current. None of the sketches here use either of these optimizations.

The DC to DC converter option requires extra components, which the NanoV2 has but, which 'bare' nRF52 boards, like SkyLab, will not, so these sketches don't use it. As for disabling the UART, with this library, provided your sketch does not call Serial.begin(), the UART does not use any noticeable current, compared to the 100nA already being used. Finally, testing with current limited supplies shows that either of these optimizations result is higher start up current. See Very Low Power BLE, Part 2 – Power Supplies (under construction) for the details.

Step 7: A Low Power Timer

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

In the Blink_millisDelay.ino, above, most of the time the sketch is just calling loop() over and over again checking to see if the ledDelay has timed out. Only one loop() every second actually does anything useful, i.e turns the led on or off.
So to reduce the supply current we want to put the micro-processor to sleep until there is something to do. Here is low power version Blink using lp_timer, Blink_lp_timer.ino

#include <br>// Pin 13 has an LED connected on most Arduino boards, including NanoV2
int led = 13;
bool ledOn = false;
lp_timer ledTimer;
const unsigned long DELAY_TIME = 1000; // mS == 1sec
// the setup routine runs once when you press reset:
void setup() {
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);
  ledTimer.startTimer(DELAY_TIME, handleLedTimer);
}
void loop() {
  sleep(); // just sleep here waiting for the timer to trigger
}
void handleLedTimer() {
  ledOn = !ledOn; // toggle state
  if (ledOn) {
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  } else {
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  }
}

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

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

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

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

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

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

The lp_timer uses the low frequency / low power clock, RTC, on the nRF52. The timer accuracy depends on the accuracy of low frequency clock. Some boards use the in-chip 32.768 kHz RC oscillator with an accuracy of about 1sec per hr at 25degC, while other boards like the RedBear NanoV2 have a 32.768Hz crystal with gives better accuracy. In this library, the nRF52's RTC counter has been configured to give mS timings, with a maximum time out of ~68mins. Time outs greater than 4095000mS (68min 15 sec) will be limited to 4095000mS.

For longer time outs 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.

Extra Components Use Extra Power

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

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

Step 8: Debugging Low Power

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

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

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

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

There is some sample output from Blink_lp_timer_debug.ino

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

Some other helpful debugging methods

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

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

<p>#ifdef __cplusplus<br>extern "C"{
#endif // __cplusplus</p><p>void cprint(const char* str);
void cprintNum(const char* str, const uint32_t num);</p><p>#ifdef __cplusplus
} // extern "C"
#endif</p>

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

<p>extern "C" void cprint(const char* str) {<br>  Serial.println(str);
}</p><p>extern "C" void cprintNum(const char* str, uint32_t num) {
  Serial.print(str);  Serial.print(' ');  Serial.println(num);
}</p>

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 8) 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 utility/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.

Step 9: A Low Power BLE UART

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

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

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

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

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

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

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

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

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

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

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

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

Step 10: Sending Data Via Lp_BLESerial

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

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

This example code, lp_BLE_temp.ino, does not do any calibration or storage of measurements. It just sends the temperature once a second when connected. This example also illustrates connection/disconnection handlers to start and stop timers, but they are not really needed as ble.print() will discard any bytes written while not connected. You can also call the lp_BLESerial isConnected() method (i.e. ble.isConnected(); ) to see if there is a current connection.

#include
lp_timer tempTimer; const unsigned long DELAY_TIME = 1000; // mS == 1sec

lp_BLESerial ble;

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

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

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

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

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

Step 11: Lp_comparator

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

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

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

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

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

void loop() { sleep(); // just sleep here waiting 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 when Above and LOW when Below the reference voltage void handlePinLevelChange(int pinState) { if (ble.isConnected()) { ble.print(millis()); ble.print(','); ble.print((pinState == HIGH) ? 'H' : 'L'); ble.println(); } digitalWrite(led, pinState); // turn the LED on when Above }

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

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

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

lp_pinChange

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

Step 12: Low Power Button Debounce

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

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

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

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

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

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

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

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

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

app_sched_queue_utilization_get()

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

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

Step 13: Custom Low Power Control and Data Logging

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

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

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

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

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

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

Finally, use the Generate Code button to generate the Arduino low power sketch for the NanoV2. Here is an example menu design, lp_BLE_NanoV2_example.ino

It displays this menu when pfodApp is used to connect. This generated sketch still uses less than 100uA, both while waiting for a connection and while connected to your mobile and updating the menu/chart and logging the data.

You can also craft your own custom interactive graphical display. This tutorial, Custom Arduino Controls for Android, takes you through the steps.

Step 14: 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 100uA are easily achievable while the chip continually active to make connections and sending/receiving data.

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

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

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.

Share

    Recommendations

    • Faux-Real Contest

      Faux-Real Contest
    • Comfort Food Challenge

      Comfort Food Challenge
    • Safe and Secure Challenge

      Safe and Secure Challenge

    2 Discussions

    0
    None
    drmpf

    21 days ago

    Added note on nRF52 Low Power Optimizations under Measuring the Supply Current
    a) enabling the nRF52 DC to DC converter and
    b) disabling the Serial UART
    to reduce supply current.

    0
    None
    drmpf

    4 weeks ago

    Added note on, Issue with NRF51 unexpectedly entering debug mode, under Measuring the Supply Current