
{"id":376,"date":"2017-06-24T23:13:51","date_gmt":"2017-06-24T13:13:51","guid":{"rendered":"http:\/\/bakke.online\/?p=376"},"modified":"2022-01-22T20:10:55","modified_gmt":"2022-01-22T09:10:55","slug":"esp8266-wifi-power-reduction-avoiding-network-scan","status":"publish","type":"post","link":"https:\/\/www.bakke.online\/index.php\/2017\/06\/24\/esp8266-wifi-power-reduction-avoiding-network-scan\/","title":{"rendered":"ESP8266 WiFi power reduction &#8211; Avoiding network scan"},"content":{"rendered":"<p>Last month I wrote a <a href=\"https:\/\/www.bakke.online\/index.php\/2017\/05\/21\/reducing-wifi-power-consumption-on-esp8266-part-1\/\">series of posts<\/a> about reducing the power consumption of the ESP8266, showing how I got the power consumption of a weather station down to 54\u00a0\u03bcAh per 5 minute reporting cycle.<\/p>\n<p>At the end of that, I thought further improvements would be mainly tweaks and fine tuning, but <a href=\"https:\/\/www.bakke.online\/index.php\/2017\/05\/25\/further-reducing-wifi-power-consumption-on-esp8266\/#comment-18\">Tobias wrote a comment<\/a> explaining that it would be possible to avoid the initial network scan happening as part of the WiFi network association. \u00a0I&#8217;m happy to say that it is possible, and it works quite well indeed.<\/p>\n<p><!--more--><\/p>\n<h3>Introducing the RTC and its memory<\/h3>\n<p>When the ESP8266 goes into deep sleep, a part of the chip called the RTC remains awake. \u00a0Its power consumption is extremely low,\u00a0so the ESP8266 does not use much power when in deep sleep. \u00a0This is the component responsible for generating the wakeup signal when our deep sleep times out. \u00a0That&#8217;s not the only benefit of the RTC, though. \u00a0It also has its own memory, which we can actually read from and write to from the main ESP8266 processor.<\/p>\n<h3>WiFi quick connect<\/h3>\n<p>The WiFi.begin() method has an overload which accepts a WiFi channel number and a BSSID. \u00a0By passing these, the ESP8266 will attempt to connect to a specific WiFi access point on the channel given, thereby eliminating the need to scan for an access point advertising the requested network.<\/p>\n<p>How much time this will save depends on your network infrastructure, how many WiFi networks are active, how busy the network is and the signal strength. \u00a0It WILL save time, though, time in which the WiFi radio is active and drawing power from the batteries.<\/p>\n<p><strong>Only problem is:<\/strong> How is the ESP8266 going to know which channel and BSSID to use when it wakes up, without doing a scan?<\/p>\n<p>That&#8217;s where we can use the memory in the RTC. \u00a0As the RTC remains powered during deep sleep, anything we write into its memory will still be there when the ESP wakes up again.<\/p>\n<p>So, when the ESP8266 wakes up, we&#8217;ll read the RTC memory and check if we have a valid configuration to use. \u00a0If we do, we&#8217;ll pass the additional parameters to WiFi.begin(). \u00a0If we don&#8217;t, we&#8217;ll just connect as normal. \u00a0As soon as the connection has been established, we can write the channel and BSSID into the RTC memory, ready for next time.<\/p>\n<p>Reading and writing this memory is explained in the <a href=\"https:\/\/github.com\/esp8266\/Arduino\/blob\/master\/libraries\/esp8266\/examples\/RTCUserMemory\/RTCUserMemory.ino\">RTCUserMemory<\/a> example in the Arduino board support package, and my code below is based on that example.<\/p>\n<h3>How to do it<\/h3>\n<p>First we&#8217;ll need to define a structure that we can store in the RTC memory. \u00a0For the WiFi quick connect we only need to know the channel and BSSID. \u00a0We also need a checksum or signature to help identify good or bad data, and as the RTCUserMemory example is using a CRC32 checksum, I am doing the same. \u00a0CRC32 may be a bit overkill for such a small structure, and a signature word might have been more suitable. \u00a0However, having the CRC32 in place now will make it easier to extend the structure in the future.<\/p>\n<p>Define the structure at the global level of your sketch, so it is available from any method or function:<\/p>\n<pre><span style=\"color: #808080;\">\/\/ The ESP8266 RTC memory is arranged into blocks of 4 bytes. The access methods read and write 4 bytes at a time,<\/span>\n<span style=\"color: #808080;\">\/\/ so the RTC data structure should be padded to a 4-byte multiple.<\/span>\n<span style=\"color: #33cccc;\">struct<\/span> {\n  <span style=\"color: #33cccc;\">uint32_t<\/span> crc32;   <span style=\"color: #808080;\">\/\/ 4 bytes<\/span>\n  <span style=\"color: #33cccc;\">uint8_t<\/span> channel;  <span style=\"color: #808080;\">\/\/ 1 byte,   5 in total<\/span>\n  <span style=\"color: #33cccc;\">uint8_t<\/span> bssid[6]; <span style=\"color: #808080;\">\/\/ 6 bytes, 11 in total<\/span>\n  <span style=\"color: #33cccc;\">uint8_t<\/span> padding;  <span style=\"color: #808080;\">\/\/ 1 byte,  12 in total<\/span>\n} rtcData;<\/pre>\n<p>The RTC memory is arranged into blocks of 4 bytes, so we pad the structure to a multiple of 4 bytes. \u00a0We lead with the CRC32 checksum, followed by a single byte for the channel and 6 bytes of BSSID. \u00a0(The BSSID is the MAC address of the WiFi access point.)<\/p>\n<p>In the setup() method, before enabling the WiFi, add this code to read the settings from the RTC memory and validate the CRC32 checksum. \u00a0If the settings are valid, we set the rtcValid flag.<\/p>\n<pre><span style=\"color: #808080;\">\/\/ Try to read WiFi settings from RTC memory<\/span>\n<span style=\"color: #33cccc;\">bool<\/span> rtcValid = <span style=\"color: #33cccc;\">false<\/span>;\n<span style=\"color: #808000;\">if<\/span>( ESP.rtcUserMemoryRead( 0, (<span style=\"color: #33cccc;\">uint32_t<\/span>*)&amp;rtcData, <span style=\"color: #33cccc;\">sizeof<\/span>( rtcData ) ) ) {\n  <span style=\"color: #808080;\">\/\/ Calculate the CRC of what we just read from RTC memory, but skip the first 4 bytes as that's the checksum itself.<\/span>\n  <span style=\"color: #33cccc;\">uint32_t<\/span> crc = calculateCRC32( ((<span style=\"color: #33cccc;\">uint8_t<\/span>*)&amp;rtcData) + 4, <span style=\"color: #33cccc;\">sizeof<\/span>( rtcData ) - 4 );\n  <span style=\"color: #808000;\">if<\/span>( crc == rtcData.crc32 ) {\n    rtcValid = <span style=\"color: #33cccc;\">true<\/span>;\n  }\n}<\/pre>\n<p>Next, replace the call to WiFi.begin() with the following:<\/p>\n<pre><span style=\"color: #808000;\">if<\/span>( rtcValid ) {\n  <span style=\"color: #808080;\">\/\/ The RTC data was good, make a quick connection<\/span>\n  <strong><span style=\"color: #ff6600;\">WiFi<\/span><\/strong>.<span style=\"color: #ff6600;\">begin<\/span>( WLAN_SSID, WLAN_PASSWD, rtcData.channel, rtcData.ap_mac, true );\n}\n<span style=\"color: #808000;\">else<\/span> {\n  <span style=\"color: #808080;\">\/\/ The RTC data was not valid, so make a regular connection<\/span>\n  <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">begin<\/span>( WLAN_SSID, WLAN_PASSWD );\n}<\/pre>\n<p>Quite simple, we check to see if we have valid data from RTC and if we do, we pass the additional information to WiFi.begin(). \u00a0Otherwise, just do a normal connection.<\/p>\n<p>The loop waiting for the WiFi connection to be established becomes a little more complicated as we&#8217;ll have to consider the possibility that the WiFi access point has changed channels, or that the access point itself has been changed. \u00a0So, if we haven&#8217;t established a connection after a certain number of loops, we&#8217;ll reset the WiFi and try again with a normal connection. \u00a0If, after 30 seconds, we still don&#8217;t have a connection we&#8217;ll go back to sleep and try again the next time we wake up. \u00a0After all, it could be that the WiFi network is temporarily unavailable and we don&#8217;t want to stay awake until it comes back. \u00a0Better to go back to sleep and hope things are better in the morning&#8230;<\/p>\n<pre><span style=\"color: #33cccc;\">int<\/span> retries = 0;\n<span style=\"color: #33cccc;\">int<\/span> wifiStatus = <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">status<\/span>();\n<span style=\"color: #808000;\">while<\/span>( wifiStatus != WL_CONNECTED ) {\n  retries++;\n  <span style=\"color: #808000;\">if<\/span>( retries == 100 ) {\n    <span style=\"color: #808080;\">\/\/ Quick connect is not working, reset WiFi and try regular connection<\/span>\n    <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">disconnect<\/span>();\n    <span style=\"color: #ff6600;\">delay<\/span>( 10 );\n    <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">forceSleepBegin<\/span>();\n    <span style=\"color: #ff6600;\">delay<\/span>( 10 );\n    <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">forceSleepWake<\/span>();\n    <span style=\"color: #ff6600;\">delay<\/span>( 10 );\n    <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">begin<\/span>( WLAN_SSID, WLAN_PASSWD );\n  }\n  <span style=\"color: #808000;\">if<\/span>( retries == 600 ) {\n    <span style=\"color: #808080;\">\/\/ Giving up after 30 seconds and going back to sleep<\/span>\n    <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">disconnect<\/span>( <span style=\"color: #33cccc;\">true<\/span> );\n    <span style=\"color: #ff6600;\">delay<\/span>( 1 );\n    <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">mode<\/span>( WIFI_OFF );\n    ESP.deepSleep( SLEEPTIME, WAKE_RF_DISABLED );\n    <span style=\"color: #808000;\">return<\/span>; <span style=\"color: #808080;\">\/\/ Not expecting this to be called, the previous call will never return.<\/span>\n  }\n  <span style=\"color: #ff6600;\">delay<\/span>( 50 );\n  wifiStatus = <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">status<\/span>();\n}<\/pre>\n<p>Once the WiFi is connected, we can get the channel and BSSID and stuff it into the RTC memory, ready for the next time we wake up.<\/p>\n<pre><span style=\"color: #808080;\">\/\/ Write current connection info back to RTC<\/span>\nrtcData.channel = <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">channel<\/span>();\n<span style=\"color: #ff6600;\">memcpy<\/span>( rtcData.ap_mac, <span style=\"color: #ff6600;\"><strong>WiFi<\/strong><\/span>.<span style=\"color: #ff6600;\">BSSID<\/span>(), 6 ); <span style=\"color: #808080;\">\/\/ Copy 6 bytes of BSSID (AP's MAC address)<\/span>\nrtcData.crc32 = calculateCRC32( ((<span style=\"color: #33cccc;\">uint8_t<\/span>*)&amp;rtcData) + 4, <span style=\"color: #33cccc;\">sizeof<\/span>( rtcData ) - 4 );\nESP.rtcUserMemoryWrite( 0, (<span style=\"color: #33cccc;\">uint32_t<\/span>*)&amp;rtcData, <span style=\"color: #33cccc;\">sizeof<\/span>( rtcData ) );<\/pre>\n<p>And the calculateCRC32() method:<\/p>\n<pre><span style=\"color: #33cccc;\">uint32_t<\/span> calculateCRC32( <span style=\"color: #33cccc;\">const uint8_t<\/span> *data, <span style=\"color: #ff6600;\"><strong>size_t<\/strong><\/span> <span style=\"color: #ff6600;\">length<\/span> ) {\n  <span style=\"color: #33cccc;\">uint32_t<\/span> crc = 0xffffffff;\n  <span style=\"color: #808000;\">while<\/span>( <span style=\"color: #ff6600;\">length<\/span>-- ) {\n    <span style=\"color: #33cccc;\">uint8_t<\/span> c = *data++;\n    <span style=\"color: #808000;\">for<\/span>( <span style=\"color: #33cccc;\">uint32_t<\/span> i = 0x80; i &gt; 0; i &gt;&gt;= 1 ) {\n      <span style=\"color: #33cccc;\">bool<\/span> <span style=\"color: #ff6600;\">bit<\/span> = crc &amp; 0x80000000;\n      <span style=\"color: #808000;\">if<\/span>( c &amp; i ) {\n        <span style=\"color: #ff6600;\">bit<\/span> = !<span style=\"color: #ff6600;\">bit<\/span>;\n      }\n\n      crc &lt;&lt;= 1;\n      <span style=\"color: #808000;\">if<\/span>( <span style=\"color: #ff6600;\">bit<\/span> ) {\n        crc ^= 0x04c11db7;\n      }\n    }\n  }\n\n  <span style=\"color: #808000;\">return<\/span> crc;\n}<\/pre>\n<h3>Results<\/h3>\n<p>With this change, the wake time per reporting cycle has been reduced by 1 second, and the total power consumption is now down to 42.6\u03bcAh, 74% down from the starting point.<a href=\"https:\/\/www.bakke.online\/wp-content\/uploads\/2017\/06\/version7.png\"><br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-381\" src=\"https:\/\/www.bakke.online\/wp-content\/uploads\/2017\/06\/version7-1024x510.png\" alt=\"\" width=\"840\" height=\"418\" \/><\/a><\/p>\n<p>Many thanks to Tobias for pointing me in this direction.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last month I wrote a series of posts about reducing the power consumption of the ESP8266, showing how I got the power consumption of a weather station down to 54\u00a0\u03bcAh per 5 minute reporting cycle. At the end of that, I thought further improvements would be mainly tweaks and fine tuning, but Tobias wrote a &hellip; <a href=\"https:\/\/www.bakke.online\/index.php\/2017\/06\/24\/esp8266-wifi-power-reduction-avoiding-network-scan\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;ESP8266 WiFi power reduction &#8211; Avoiding network scan&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,25,1],"tags":[8,27,9,23,24],"class_list":["post-376","post","type-post","status-publish","format-standard","hentry","category-arduino","category-the-notwork","category-uncategorized","tag-arduino","tag-energy-efficience","tag-esp8266","tag-internet-of-things","tag-notwork"],"_links":{"self":[{"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/posts\/376","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/comments?post=376"}],"version-history":[{"count":1,"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/posts\/376\/revisions"}],"predecessor-version":[{"id":1331,"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/posts\/376\/revisions\/1331"}],"wp:attachment":[{"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/media?parent=376"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/categories?post=376"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bakke.online\/index.php\/wp-json\/wp\/v2\/tags?post=376"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}