Skip to content
ESP8266 edited this page Apr 21, 2015 · 15 revisions

Welcome to the low_power_voltage_measurement wiki!

Designing An Ultra Low Power Sensor Solution With ESP8266

Introduction

In this tutorial, we describe the procedure for designing ultra-low power sensors with ESP8266. There are in general 2 types of sensors:

  • The system is woken up by events which power on ESP8266. Depending on the conditions, the system may send an alert to the server.
  • The system is woken up periodically, to measure some parameters. The system may store some parameters in the RTC memory. The system periodically sends the data to the server.

In this article, we focus on the second type of solution, where we program ESP8266 to wake up every 1 minute, measures its own supply voltage and sends measurement data to the server at every 20th iterations.

Setting Up The Server Account

First logon to http://IOT.ESPRESSIF.CN. This is Espressif’s own data server for internal testing, and open to public as well. It’s free; and there is no guarantee with regards to this service provided. We can find some information about this service at https://IOT.ESPRESSIF.CN/#/help-en/.
If you have already setup an account, and wish to configure the language settings, please login and go to settings and configure your profile. If you have not, please create an account at the above webpage. Next we have to create a device instance as shown in Figure 1. If we need to create many devices, e.g. in production, we can use batch create.

After the device is created, we have to create the datastream. In our example, we create a 2-dimensional datastream for our device, because the device sends a set of 20 measurements to the server each time, with the same timestamp. By adding another dimension, which is an index number, we can infer the actual timestamp for each individual measurement. Please take note of the datastream field, because we will need it in the source codes to send the data to this.

The Various Device Keys

There are 2 device keys that we need to care about, of which the master device key is needed for the device:

  • Device Key (Master): Used by the device to identify itself to the server. It is to be stored in the device’s flash.
  • Device Key (Owner): Used by the mobile device to identify itself as the owner of this device. This owner has root access and can request the server to generate other device keys with lower privileges that can be shared to other users.

For our application, we have a use the master device key to read the data from the server, even though we really shouldn't. (see script/get_sensor_data.py.)

Setting Up The Chip

First in order to compile programs for ESP8266, we need to build the XTENSA CROSSTOOL-NG toolchain. The instructions for doing so can be found at:

Next, we download the source codes for our particular application from this repository.

Code Customization

Sleep Time, Communication and Sensor Device

The sleep timing and polling rate can be adjusted in the LOW_POWER_SENSOR_DEMO_DIR/app/include/user_config.h file.

#if HUMITURE_SUB_DEVICE
#define SENSOR_DEEP_SLEEP_TIME  60000000  // Count in microseconds, i.e. 1 min.
#define SENSOR_CONNECT_TIME     10000000  

The amount of data to be stored is found in app/include/driver/sensor_lp.h.

#define SENSOR_DATA_NUM        20  // No of iterations to store in RTC memory
#define SENSOR_DATA_MEM_ADDR   120
#define INIT_MAGIC             0x7E7E55AA
#define RTC_CNT_ADDR           120
#define DSLEEP_TIME            SENSOR_DEEP_SLEEP_TIME
#define SENSOR_DATA_SET_LEN    50  //max LEN of data, e.g. {“x”:1,”y”:351}

To simplify the application, the program flow is contained within a single function; to change the sensor device or the actions to perform, we have to modify the data_func() function in

__LOW_POWER_SENSOR_DEMO_DIR__/app/driver/sensor_lp.c. 

We list this function here with comments:

#include "ets_sys.h"
#include "osapi.h"
#include "c_types.h"
#include "user_interface.h"
#include "user_devicefind.h"
#include "user_webserver.h"
#include "user_esp_platform.h"
#include "driver/sensor_lp.h"

SENSOR_DATA_RTC_MEM sensor_data ;
SENSOR_DATA_RTC_MEM* ICACHE_FLASH_ATTR get_sensor_data() { return &sensor_data; }

void ICACHE_FLASH_ATTR data_func() {
// Read out the sensor data structure from RTC memory
system_rtc_mem_read( SENSOR_DATA_MEM_ADDR, &sensor_data, sizeof(SENSOR_DATA_RTC_MEM) );

// When the system powers on for the first time, the data in the rtc memory is random.
struct esp_platform_saved_param esp_param_t;
user_esp_platform_load_param(&esp_param_t);  // Stored in flash
// Load user params to check if the device was successfully registered to the server
// If it wasn't, it usually returns 255 (from the flash.)

if(sensor_data.init_flg!=INIT_MAGIC || sensor_data.cnt>SENSOR_DATA_NUM ) {
    // This case runs when we first power on or when it time to flush the RTC memory of old data.
    if(esp_param_t.activeflag!=1) {   // If registered & activated
        user_esp_platform_init();     // Router is not configured. Setup softAP. Wait for config. 
        #ifdef SERVER_SSL_ENABLE
        user_webserver_init(SERVER_SSL_PORT);
        #else
        user_webserver_init(SERVER_PORT);
        #endif
    } else { // was connected! So we set init magic to exit the setup loop
        sensor_data.init_flg = INIT_MAGIC;
        sensor_data.cnt = 0;
        system_rtc_mem_write(SENSOR_DATA_MEM_ADDR, &sensor_data, sizeof(SENSOR_DATA_RTC_MEM));
        __SET__DEEP_SLEEP__WAKEUP_NO_RF__; 
        system_deep_sleep(100000); 
    }
} else { // This is where the measurements are made
    uint16 vdd_val = 0;
    if(sensor_data.cnt<0 || sensor_data.cnt>=SENSOR_DATA_NUM) 
        sensor_data.cnt=0; // range check and resets counter if needed

    /* Reads power supply voltage, byte 107 of init_data.bin should be set to 0xFF.
    *  Replace with your own code.*/
    sensor_data.data[sensor_data.cnt++] = (uint16)(phy_get_vdd33());
    system_rtc_mem_write( SENSOR_DATA_MEM_ADDR, &sensor_data, sizeof(SENSOR_DATA_RTC_MEM) );

    // Setup next sleep cycle
    if(sensor_data.cnt==SENSOR_DATA_NUM-1) { __SET__DEEP_SLEEP__WAKEUP_NORMAL__; }
    else { __SET__DEEP_SLEEP__WAKEUP_NO_RF__; }

    // Uploads or go to sleep
    if(sensor_data.cnt == SENSOR_DATA_NUM) { user_esp_platform_init(); }
    else { system_deep_sleep(SENSOR_DEEP_SLEEP_TIME); }
}
}

Uploading Data

To change the format of the data to upload, edit the LOW_POWER_SENSOR_DEMO_DIR/app/include/driver/sensor_lp.h file. For instance, for our particular application, we used:

#define SENSOR_DATA_STREAM "supply-voltage"

where "supply-voltage", matches the datastream field that we used when we created the device.

The datastream supply-voltage is a 2-dimensional variable, because we are uploading an array of voltages, stored in the RTC memory all at the same time. Each time we upload a set of values, they have the same timestamp; we have to infer their true timestamp from their indices.

Performing Other Actions Upon Wakeup

We can configure the chip to perform other functions upon waking up, e.g. to turn on some external regulators, or sensors. We can configure the chip to perform other functions before going to sleep, e.g. to turn off some external regulators or sensors to save power. This will be the subject of our next project.

Compiling the Codes

To compile the source code, open a terminal, and go to the directory:

cd __LOW_POWER_SENSOR_DEMO_DIR__/app

Next, make sure that the compile script has executable permission and run it:

chmod u+rx gen_misc.sh
./gen_misc.sh

We will be prompted with the following options:

STEP 1: choose boot version (0=boot_v1.1, 1=boot_v1.2+, 2=none)
STEP 2: choose bin generate (0=eagle.flash.bin+eagle.irom0text.bin, 1=user1.bin, 2=user2.bin)
STEP 3: choose SPI speed (0=20MHz, 1=26.7MHz, 2=40MHz, 3=80MHz)
STEP 4: choose SPI mode (0=QIO, 1=QOUT, 2=DIO, 3=DOUT)
STEP 5: choose SPI size (0=256KB, 1=512KB, 2=1024KB, 3=2048KB, 4=4096KB)

The script compiles several versions of binary. We note the following:

  • eagle.flash.bin + eagle.irom0text.bin
    • The earliest version of binaries.
    • Only support 512kB flash map.
    • If our flash device is >512kB, the script with still work if we select 512kB.
    • Does not support OTA upgrade.
  • boot_v1.1.bin + user1.old.bin/user2.old.bin
    • Add a bootstrap to load programs from user1.bin or user2.bin.
    • Supports over the air (OTA) upgrades.
    • Supports different flash size.
  • boot_v1.2.bin + user1.new.bin/user2.new.bin
    • Functionally the same as boot_v1.1 but more optimized.

Based on our characterization of many flash devices, we recommend that setting 40MHz for the flash interface speed. By using the default values for the compile script, it should usually work. Thereafter, the system compiles the codes and generates the bin files and reports accordingly:

No boot needed.
Generate eagle.flash.bin and eagle.irom0text.bin successfully in folder bin.
eagle.flash.bin-------->0x00000
eagle.irom0text.bin---->0x40000
file name :  ../bin/eagle.flash.bin

Writing into Flash

We can now write the program into the Flash device of the ESP8266 module. For my Mac OS X Yosemite setup, I used the following command:

cd __LOW_POWER_SENSOR_DEMO_DIR__/bin
./write_flash.sh

The write_flash.sh command now runs ../tools/add_disable_rf_cmd.py automatically. So you don't have to do this anymore. However, you have to check the frequency of your crystal on the ESP8266 board. If it is 40MHz, you have to edit the write_flash.sh command to use "esp_init_data_vdd33.bin_40M" for 0x7C000. If the crystal is 26MHz, use "esp_init_data_vd33.bin" instead. They only differ at byte 48, which helps the system to distinguish between the 2.

There are many versions of ESPTOOL available online; my favorite version is the original version by TheMadInventor: https://github.com/themadinventor/esptool

write_flash.sh uses ESPTOOL.py. Please download the file and put in the tools directory.

Please modify the contents of write_flash.sh as per your requirement. /dev/tty.usbserial-AL00D9W2 is the name that my computer assigns to the USB dongle. We have to generate our own device key as well, and replace master_device_key.txt with our version, and run gen_key_bin.py, to generate our key.bin file.

When the system boots up, we can configure ESP8266 to connect to the router via LOW_POWER_SENSOR_DEMO_DIR/scripts/setup_AP_information.py. To do so, we edit the script to fill in the router SSID and password.

Magic Sauce (Enable Hidden Low Power Features)

In order to enable some undocumented low power features of ESP8266, we have added the following codes in the LOW_POWER_SENSOR_DEMO_DIR/app/Makefile, that modifies part of the flash content.

@python ../tools/add_disab1e_rf_cmd.py ../bin/upgrade/$(BIN_HOME).bin

This magic sauce command, lowers the power consumption of the chip by 30% when it wakes up to perform sensor measurement.

Reading Sensor Data

If all goes well, the sensor will start reading our supply voltage once every minute, and after 20 readings, it will send the data to the server. We can read the data by using LOW_POWER_SENSOR_DEMO_DIR/scripts/plot_sensor_voltage.py and LOW_POWER_SENSOR_DEMO_DIR/scripts/get_sensor_voltage.py.

These scripts automatically checks for the device key from the bin directory -- they look for the key.bin file.

Troubleshooting

Check if you have connected the RESET pin to GPIO16. GPIO16 is used to wake the system. For this application, if we have not done so, it will sleep forever. However, when writing to flash, I think we should not connect the RESET pin to GPIO16.

If you are not getting any intelligible data via UART when debugging, check that you have used the correct version of esp_init_data_vdd33.bin in your write_flash.sh script. Check the section "Writing into Flash".

#What Has Changed? So what has changed? We have lowered the standby current, by simply turning off the flash device when we are asleep. The codes are cleaner now. Hopefully, they are comprehensible and clear. The data uploading is embedded within the user_esp_platform.c file. It's rather complicated and is done via callbacks. I hope to clean part up as well. Soon.