-
Nanosleeper - Sub 100nA Deep Sleep Dev Board
01/28/2024 at 18:24 • 0 commentsI used MetaShunt to test my new ultra-low power development board, called Nanosleeper. The goal with Nanosleeper was to achieve <100nA deep sleep current with the ability to wake up at a selected time. It was built in the Adafruit Feather form factor, but does not 100% match the Feather specification. It has support for controlling external power and I2C pullups for external devices so that very, VERY low power systems can be prototyped.
Debugging and testing a product like Nanosleeper is difficult without a tool like MetaShunt. The power used when on is over 8,000X times higher than when in deep sleep, so measuring current with a simple shunt is not likely to work. However, MetaShunt has no issues with this.
Pictures of an assembled Nanosleeper and it connected to MetaShunt are shown below. Nanosleeper is powered by the 3.3V supply from MetaShunt into the BAT input of Nanosleeper. Because Nanosleeper uses an extremely low power linear voltage regulator (1.8V logic level), this gives the same current results as it would if powered by a 3V coin cell battery.
The firmware used during this test is shown below. The two onboard LEDs are turned on for 5 seconds, then turned off. A 2 second busy wait then occurs. Next, the RTC is set up using I2C, and set to wake the system after 15 seconds. The wakeup pins are then configured and shutdown sleep mode is entered.
// Toggle LED to indicate awake HAL_GPIO_WritePin(D12_GPIO_Port, D12_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(D13_GPIO_Port, D13_Pin, GPIO_PIN_SET); HAL_Delay(5000); HAL_GPIO_WritePin(D12_GPIO_Port, D12_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(D13_GPIO_Port, D13_Pin, GPIO_PIN_RESET); HAL_Delay(2000); // Set up RTC, checking if it's already been set up bool is_initial_setup = true; uint32_t current_time_utc = 1705968815; bool rv_3028_setup_ok = init_rv_3028_c7(hi2c1, current_time_utc, is_initial_setup); // Go to shutdown mode, with pin wakeup. HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1); /* Enable wakeup pin WKUP1 */ HAL_PWREx_EnableGPIOPullDown(PWR_GPIO_C, PWR_GPIO_BIT_14); HAL_PWREx_EnableGPIOPullDown(PWR_GPIO_C, PWR_GPIO_BIT_15); HAL_PWREx_EnableGPIOPullUp(PWR_GPIO_A, PWR_GPIO_BIT_0); HAL_PWREx_EnablePullUpPullDownConfig(); HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1_LOW); HAL_PWREx_DisableInternalWakeUpLine(); /* enter shutdown */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WUF1); HAL_PWREx_EnterSHUTDOWNMode();
Each of those sections of the firmware are very clear in the power profile below. The system starts up (the current spike off the chart is due to plugging in Nanosleeper to power) and with both LEDs on the current is steady at about 800uA. With the LEDs off, the current drops to about 600uA. There is a current spike to about 1.15mA during the I2C communication with the RTC, and then the system goes into deep sleep. After 15 seconds, the system starts up again and turns on the LEDs.
The deep sleep current is shown in higher detail below, and is steady just under 95nA.
This example shows MetaShunt's performance for an ultra-low power design, with ~8,000X variation in power consumption during operation. -
Example: ESP32 WiFi and Low Power Modes
11/29/2023 at 01:33 • 0 commentsThe ESP32 is a commonly used wifi and Bluetooth enabled microcontroller which nicely demonstrates the usefulness of MetaShunt as a measurement and debugging tool. When connected to WiFi and operating at full speed, the ESP32 can consume a lot of power (some sources report up to 240mA maximum). However, it also has low power modes that can get the current consumption down to about 5uA, a level 48,000 times lower than its maximum! MetaShunt is the perfect tool for analyzing the power of such a system. A multimeter is incapable of keeping up with the rapid changes in power consumption, and multiple ranges would likely be needed. A simple current shunt monitored by an oscilloscope would suffer from resolution issues - a shunt sized for 240mA will not be useful at 5uA, and a shunt sized for 5uA will not work at 240mA (the system would brown out). MetaShunt can easily measure current across these ranges without browning out the system.
This test was completed with an ESP32 Huzzah development board from Adafruit, since it is what I had on-hand. This is not the ideal board to test low-power consumption with, because it has a UART to USB adapter component and a battery charger on board, neither of which can be put into low power mode. Nonetheless, the microcontroller portion of it can be controlled to enter and exit low power modes, as well as connect to WiFi, and I will demonstrate monitoring this system using MetaShunt.
The board was connected to MetaShunt by the 5V and VSNS pins, just like in the prior log. Note that the measurements will therefore also include quiescent current from the voltage regulator.
The following Arduino sketch was used in this test. Various active busy waits, waits while connected to WiFi, online data queries, and low power modes are demonstrated. Care is taken before entering deep sleep to turn on the RTC for wakeup later and to set all GPIO into the best power saving mode (this is really important for getting the last few uA out of a low power system).
#include <WiFi.h> #include "time.h" #include "driver/rtc_io.h" #include "driver/gpio.h" #include "esp_wifi.h" #include "driver/adc.h" //#define USE_SERIAL 1 RTC_DATA_ATTR int bootCount = 0; const char* ssid = "MYSSID"; const char* password = "MYPASSWORD"; const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 3600; const int daylightOffset_sec = 3600; void printLocalTime() { struct tm timeinfo; if(!getLocalTime(&timeinfo)){ Serial.println("Failed to obtain time"); return; } Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); } void setup() { #ifdef USE_SERIAL Serial.begin(115200); #endif pinMode(13,OUTPUT); delay(500); digitalWrite(13,HIGH); delay(2500); digitalWrite(13,LOW); //connect to WiFi #ifdef USE_SERIAL Serial.printf("Connecting to %s ", ssid); #endif WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(15); #ifdef USE_SERIAL Serial.print("."); #endif } #ifdef USE_SERIAL Serial.println(" CONNECTED"); #endif //init and get the time configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); delay(200); int i; for(i = 0; i < 10; i++) { digitalWrite(13,!digitalRead(13)); delay(100); } digitalWrite(13,LOW); #ifdef USE_SERIAL printLocalTime(); #endif //disconnect WiFi as it's no longer needed WiFi.disconnect(true); WiFi.mode(WIFI_OFF); esp_sleep_enable_timer_wakeup(3000000); // 3 sec esp_light_sleep_start(); // we'll wake from light sleep here //Set things up for lower power // ESP32 docs say to isolate pin 12 in deep sleep to save power rtc_gpio_isolate(GPIO_NUM_12); // Isolate other pins rtc_gpio_isolate(GPIO_NUM_39); rtc_gpio_isolate(GPIO_NUM_36); rtc_gpio_isolate(GPIO_NUM_35); rtc_gpio_isolate(GPIO_NUM_34); rtc_gpio_isolate(GPIO_NUM_32); rtc_gpio_isolate(GPIO_NUM_33); rtc_gpio_isolate(GPIO_NUM_27); rtc_gpio_isolate(GPIO_NUM_26); rtc_gpio_isolate(GPIO_NUM_25); rtc_gpio_isolate(GPIO_NUM_23); rtc_gpio_isolate(GPIO_NUM_22); rtc_gpio_isolate(GPIO_NUM_21); rtc_gpio_isolate(GPIO_NUM_19); rtc_gpio_isolate(GPIO_NUM_18); rtc_gpio_isolate(GPIO_NUM_17); rtc_gpio_isolate(GPIO_NUM_16); rtc_gpio_isolate(GPIO_NUM_15); rtc_gpio_isolate(GPIO_NUM_14); rtc_gpio_isolate(GPIO_NUM_13); rtc_gpio_isolate(GPIO_NUM_5); rtc_gpio_isolate(GPIO_NUM_4); rtc_gpio_isolate(GPIO_NUM_2); rtc_gpio_isolate(GPIO_NUM_0); esp_wifi_stop(); adc_power_off(); // hold GPIOs gpio_deep_sleep_hold_en(); // wake up 1 second later and then go into deep sleep esp_sleep_enable_timer_wakeup(4000000); // 4 sec // Use auto mode to turn off only if not needed by wakeup mode esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_AUTO); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_AUTO); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO); esp_deep_sleep_start(); } void loop() { }
Measurements were then taken of this system. We can see in the image below that the power varies widely. This system is more complicated than the previous example, in large part because the ESP32 has two cores, and the networking core software is not shown here.
Let's look at each section of code and match it to the data! First, the LED pin is set high for 2.5 seconds and then turned off.
#ifdef USE_SERIAL Serial.begin(115200); #endif pinMode(13,OUTPUT); delay(500); digitalWrite(13,HIGH); delay(2500); digitalWrite(13,LOW);
We can see an initial section with slightly lower current, and then it jumps up slightly to about 60.6mA during the busy wait with LED on.
The WiFi is then connected, current time is pulled from the internet, and the LED is toggled 10 times over 1 second.
//connect to WiFi #ifdef USE_SERIAL Serial.printf("Connecting to %s ", ssid); #endif WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(15); #ifdef USE_SERIAL Serial.print("."); #endif } #ifdef USE_SERIAL Serial.println(" CONNECTED"); #endif //init and get the time configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); delay(200); int i; for(i = 0; i < 10; i++) { digitalWrite(13,!digitalRead(13)); delay(100); } digitalWrite(13,LOW); #ifdef USE_SERIAL printLocalTime(); #endif
At the start, when connecting, the current jumps up very high, above 1A. It then drops down to roughly stable around 275mA. We see a second spike which presumably is when the time is queried from the internet. The current then drops lower and we see periodic spikes. These spikes seem to be from the WiFi stack - the ESP32 is periodically maintaining its connection, likely matching to the DTIM interval. When zoomed in on the second image below, the base current can be seen to increase and decrease as the LED is toggled.
Next, the WiFi is turned off and light sleep is used.
//disconnect WiFi as it's no longer needed WiFi.disconnect(true); WiFi.mode(WIFI_OFF); esp_sleep_enable_timer_wakeup(3000000); // 3 sec esp_light_sleep_start(); // we'll wake from light sleep here
We can see the current drop to around 7.7mA, with spikes up to approximately 60mA at about 14Hz. It is not clear what is causing that.
Finally, the ESP32 goes into deep sleep mode. Before we enter that mode, we set the pins into an efficient state.
//Set things up for lower power // ESP32 docs say to isolate pin 12 in deep sleep to save power rtc_gpio_isolate(GPIO_NUM_12); // Isolate other pins rtc_gpio_isolate(GPIO_NUM_39); rtc_gpio_isolate(GPIO_NUM_36); rtc_gpio_isolate(GPIO_NUM_35); rtc_gpio_isolate(GPIO_NUM_34); rtc_gpio_isolate(GPIO_NUM_32); rtc_gpio_isolate(GPIO_NUM_33); rtc_gpio_isolate(GPIO_NUM_27); rtc_gpio_isolate(GPIO_NUM_26); rtc_gpio_isolate(GPIO_NUM_25); rtc_gpio_isolate(GPIO_NUM_23); rtc_gpio_isolate(GPIO_NUM_22); rtc_gpio_isolate(GPIO_NUM_21); rtc_gpio_isolate(GPIO_NUM_19); rtc_gpio_isolate(GPIO_NUM_18); rtc_gpio_isolate(GPIO_NUM_17); rtc_gpio_isolate(GPIO_NUM_16); rtc_gpio_isolate(GPIO_NUM_15); rtc_gpio_isolate(GPIO_NUM_14); rtc_gpio_isolate(GPIO_NUM_13); rtc_gpio_isolate(GPIO_NUM_5); rtc_gpio_isolate(GPIO_NUM_4); rtc_gpio_isolate(GPIO_NUM_2); rtc_gpio_isolate(GPIO_NUM_0); esp_wifi_stop(); adc_power_off(); // hold GPIOs gpio_deep_sleep_hold_en(); // wake up 1 second later and then go into deep sleep esp_sleep_enable_timer_wakeup(4000000); // 4 sec // Use auto mode to turn off only if not needed by wakeup mode esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_AUTO); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_AUTO); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO); esp_deep_sleep_start();
The current at this point drops down to approximately 6.7mA. This is much higher than the hoped-for 5uA. It appears to be due to the UART-USB converter component which is powered on at all times on this development board. Here, we still see the periodic spikes in current as well.
-
Example: Power of Feather M0 Adalogger
11/15/2023 at 01:37 • 0 commentsAs an example to show MetaShunt's capability, I created an Arduino sketch for the Adafruit Feather M0 Adalogger which would toggle the onboard LED and use various low power modes. This microcontroller development board was then connected to MetaShunt:
Adalogger GND <----> VSNS
Adalogger USB <----> 5V
The code running on the Adalogger is the following:
#include "LowPower.h" #include <RTCCounter.h> #define LED_PIN 13 #define CONFIG_PINS true #define DISABLE_TC_TCC true #define DISABLE_CLOCKS true #define DISABLE_ADC true void setup() { pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); #ifdef CONFIG_PINS pinMode(0, OUTPUT); pinMode(1, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); pinMode(7, OUTPUT); pinMode(8, OUTPUT); pinMode(9, OUTPUT); pinMode(10, OUTPUT); pinMode(11, OUTPUT); pinMode(12, OUTPUT); pinMode(A0, OUTPUT); pinMode(A1, OUTPUT); pinMode(A2, OUTPUT); pinMode(A3, OUTPUT); pinMode(A4, OUTPUT); pinMode(A5, OUTPUT); digitalWrite(0, LOW); digitalWrite(1, LOW); digitalWrite(4, LOW); digitalWrite(5, LOW); digitalWrite(6, LOW); digitalWrite(7, LOW); digitalWrite(8, LOW); digitalWrite(9, LOW); digitalWrite(10, LOW); digitalWrite(11, LOW); digitalWrite(12, LOW); digitalWrite(A0, LOW); digitalWrite(A1, LOW); digitalWrite(A2, LOW); digitalWrite(A3, LOW); digitalWrite(A4, LOW); digitalWrite(A5, LOW); #endif #ifdef DISABLE_TC_TCC TC3->COUNT16.CTRLA.bit.ENABLE = 0; TC4->COUNT16.CTRLA.bit.ENABLE = 0; TC5->COUNT16.CTRLA.bit.ENABLE = 0; TCC0->CTRLA.bit.ENABLE = 0; TCC1->CTRLA.bit.ENABLE = 0; TCC2->CTRLA.bit.ENABLE = 0; #endif #ifdef DISABLE_CLOCKS SYSCTRL->DFLLCTRL.bit.RUNSTDBY = 0; SYSCTRL->VREG.bit.RUNSTDBY = 0; #endif #ifdef DISABLE_ADC ADC->CTRLA.bit.SWRST = 1; #endif rtcCounter.begin(); } void loop() { // Busy wait delay(2000); int i; for(i=0; i<10; i++) { delay(250); digitalWrite(LED_PIN, HIGH); delay(250); digitalWrite(LED_PIN, LOW); } // Another type of sleep rtcCounter.setPeriodicAlarm(2); SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // Disable SysTick interrupts LowPower.idle(IDLE_2); SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; // Enable SysTick interrupts if (rtcCounter.getFlag()) { rtcCounter.clearFlag(); } delay(500); // Then standby rtcCounter.setPeriodicAlarm(5); LowPower.standby(); if (rtcCounter.getFlag()) { rtcCounter.clearFlag(); } delay(1000); for(i=0; i<2; i++) { delay(500); digitalWrite(LED_PIN, HIGH); delay(500); digitalWrite(LED_PIN, LOW); } }
This code toggles LEDs and uses active mode (in delays), idle, and standby power modes.
Using the Python interface script that will be released soon, data was recorded for 20 seconds. The output data was provided at 4kHz (the onboard measurement is much faster, but USB bandwidth limits the output data rate to this level). The overall data cycle is shown below.
Let's look specifically at sections of the code and see how they match to the current profile shown here. The first section involves a 2 second busy wait followed by toggling the LED pin 10 times with a 0.5s period.
// Busy wait delay(2000); int i; for(i=0; i<10; i++) { delay(250); digitalWrite(LED_PIN, HIGH); delay(250); digitalWrite(LED_PIN, LOW); }
The plot below shows the measurements during this portion of the program. During the busy wait, the current is ~11.6mA, and it increases to about 12.2mA when the LED is turned on.
In the next section, the Idle 2 sleep mode is used. This sleep mode is set for about 2 seconds. Then a 500ms busy wait occurs.
// Another type of sleep rtcCounter.setPeriodicAlarm(2); SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // Disable SysTick interrupts LowPower.idle(IDLE_2); SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; // Enable SysTick interrupts if (rtcCounter.getFlag()) { rtcCounter.clearFlag(); } delay(500);
The measurement profile for this portion of the code is shown here. The current in Idle mode drops down to below 4mA, and back up for 500ms during the busy wait.
In the final portion of the code, the system enters standby for 5 seconds, followed by 1 second busy wait, and toggling the LED twice at 1Hz.
// Then standby rtcCounter.setPeriodicAlarm(5); LowPower.standby(); if (rtcCounter.getFlag()) { rtcCounter.clearFlag(); } delay(1000); for(i=0; i<2; i++) { delay(500); digitalWrite(LED_PIN, HIGH); delay(500); digitalWrite(LED_PIN, LOW); }
The profile for this section is shown in the next image. When in standby, the current drops very low for about 5 seconds. The busy wait and pin toggling are also visible.
The standby mode current can be focused on as well. In the lowest power mode, the current is about 160uA - not very low but not bad for a development board not specifically designed for low power!
This example doesn't show all of the capabilities of MetaShunt. In particular, the ratio between the peak and minimum current is only about 75:1. A simple current shunt could measure this with reasonable accuracy, This tool is designed to handle variation across much wider scales, up to approximately 10,000,000:1. Future examples will show systems with significantly larger variation between peak and minimum current, as well as examples of the use of this tool in optimizing a low power system.