The Si7021 is the I2C temperature and humidity sensor used in this device, and it is polled every 20 seconds for data. The current usage for a I2C poll for data looks like:
The orange trace is the I2C Clock line, the green trace is I2C data line, and the yellow trace is the output from the uCurrent. The uCurrent is in 1mV/uA range, so the plateau at 1.38 V indicates 1.38 mA. The bursts of activity on the I2C lines, indicated by the blue arrows (first time I've ever tried the annotation feature of my oscilloscope), are data being transferred between the uController and the Si7021. The burst on the left is the STM32L0 sending an 0xE5 to the device, which means get the humidity reading using what is known as clock stretching to hold the uC while the Si7021 measures the humidity and temperature. About 17 mS later, the sensor releases the I2C bus, and sends the data to the controller.
I2C uses open drain signaling, which means a pull up resistor is connected between the I2C pin and VCC. I2C can be run at several different speeds, 100kHz, 400kHz, and 1Mhz, and the capacitance of the lines, and the amount of current flowing, dictate how fast you can run. Since I'm using fairly high value pull up resistors of 10K to keep the current usage low, I set the speed of the bus at 100kHz.
Looking at the above trace, you can see the 17 mS the sensor needs to measure the temperature and humidity is an excellent time to put the microcontroller asleep. There is a feature in the STM32L0 series of controllers that allows the use of the I2C bus to wake the controller from a sleep mode. While the documentation states this wake feature only work in slave mode, I decided to spend a few hours seeing if I was clever enough to make it work in master mode. I wasn't.
Using DMA, though, it is possible to send the 0xE5 command, put the uC asleep for 15 mS, and then awake, wait a bit longer, and read the data:
During the 15 mS sleep, the current usage dropped from about 1.40 mA to 1 mA, and the overall energy usage dropped 20% (from 6.95E-6mAh to 5.5E-6 mAh).
To improving this further, you must think about how clock stretching works. The slave device, in this case the sensor, pulls the I2C clock line down until it is ready to send its data. This means that the 10K pull up resistor has 3.0 V across it, and that is using 300 uA. To make matters worse, as you can see in the oscilloscope trace, the data line is also being held low by the uC, and that is using another 300 uA. That's 600 uA, more than half a milliamp, being burnt and not really doing anything useful.
The Si7021 has a polling mode, the 0xF5 command, where it will start to measure the environment, and the uC repeatedly tries to read the data, getting NACKs until the sensor is ready with the data. While this is a more complicated programming task than simply issuing the read command, and the system automatically waits until the sensor is ready, and none of the I2C lines are held low for extended periods of time:
In this case, both the data and clock I2C lines are high during the 20 ms sleep phase. Once the sensor is finished with the humidity and temperature measurement, a measurable current drop is even visible as the sensor goes to sleep, as indicated by more of the fancy annotations. After some testing, I found it is much more energy efficient to over-sleep, and request the data some time after the sensor has completed the measurement, rather than under-sleep, and have to poll until the sensor is ready. I chose a 20 ms sleep. This sleep-then-poll workflow results in about 1/3rd the energy usage compared to the simple clock stretching method (1.91E-6 mAh vs 6.95E-6 mAh).