Deep sleep and timers causing Exception 28 in ESP-12F

Hello, this is my first post so I hope I’m doing everything properly.
I’m setting up an ESP-12F with a servomotor to control an analogic thermostate.
As there are no power sockets near where it will be installed I will use a LiFePo4 batery to power it, which means: deep sleep.
I have sucessfully installed and configured the blynkEdgent example (I need OTA updates) and it works properly when the code is constantly run without the deep sleep. But once I add it, anytime I tries to go into deep sleep, the board reboots and the following exception is thrown:

Exception 28: LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads
PC: 0x4000df2f
EXCVADDR: 0x00000033
Decoding stack results
0x40100a48: pvPortMalloc(size_t, char const*, int) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\umm_malloc/umm_heap_select.h line 85
0x4021cd2d: _svfprintf_r at /workdir/repo/newlib/newlib/libc/stdio/nano-vfprintf.c line 662
0x4020a9d2: ESP8266WiFiSTAClass::begin(char const*, char const*, int, unsigned char const*, bool) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\libraries\ESP8266WiFi\src\ESP8266WiFiSTA.cpp line 198
0x4020a8fb: ESP8266WiFiSTAClass::begin(char const*, char const*, int, unsigned char const*, bool) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\libraries\ESP8266WiFi\src\ESP8266WiFiSTA.cpp line 154
0x4010114c: realloc(void*, size_t) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 853
0x40100f0b: umm_free_core(umm_heap_context_t*, void*) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 549
0x40101114: malloc(size_t) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 821
0x401010ea: free(void*) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 595
0x40100a6c: vPortFree(void*, char const*, int) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\heap.cpp line 322
0x402270ec: mem_free at core/mem.c line 236
0x40220c62: pbuf_free_LWIP2 at core/pbuf.c line 786
0x402242ed: dhcp_renew at core/ipv4/dhcp.c line 1196
0x4020aa28: ESP8266WiFiSTAClass::begin(char*, char*, int, unsigned char const*, bool) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\libraries\ESP8266WiFi\src\ESP8266WiFiSTA.cpp line 212
0x402030dc: enterConnectNet() at C:\Users\ssbj\Documents\Arduino\Termostato_new/ConfigMode.h line 361
0x40100821: millis() at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\core_esp8266_wiring.cpp line 193
0x40100821: millis() at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\core_esp8266_wiring.cpp line 193
0x402138b4: uart_rx_available(uart_t*) at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\uart.cpp line 228
0x4020e980: HardwareSerial::available() at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\HardwareSerial.cpp line 111
0x40202e10: BlynkConsole::run() at C:\Users\ssbj\Documents\Arduino\libraries\Blynk\src/Blynk/BlynkConsole.h line 190
0x40208fee: BlynkTimer::run() at C:\Users\ssbj\Documents\Arduino\libraries\Blynk\src\utility\BlynkTimer.cpp line 60
0x4020a808: ESP8266WiFiSTAClass::status() at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\libraries\ESP8266WiFi\src\ESP8266WiFiSTA.cpp line 527
0x40208e6d: loop() at C:\Users\ssbj\Documents\Arduino\Termostato_new/BlynkEdgent.h line 106
0x4021229c: loop_wrapper() at C:\Users\ssbj\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.2\cores\esp8266\core_esp8266_main.cpp line 201

The code I’m running is the following:



// Fill-in information from your Blynk Template here
#define BLYNK_TEMPLATE_ID "TMPLHb0atsOI"
#define BLYNK_DEVICE_NAME "Termostato"

#define BLYNK_FIRMWARE_VERSION        "0.1.0"

#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG

#define APP_DEBUG

// Uncomment your board, or configure a custom board in Settings.h
//#define USE_SPARKFUN_BLYNK_BOARD
//#define USE_NODE_MCU_BOARD
//#define USE_WITTY_CLOUD_BOARD
//#define USE_WEMOS_D1_MINI

#include "BlynkEdgent.h"

#define SLEEP_LENGTH 10 // In seconds

#include <Servo.h>
#define servoPin 5
Servo myservo;
int servoAngle;
int tempObj;
int tempSer;
BlynkTimer timer;

BLYNK_CONNECTED() {
  Serial.println("BLYNK_CONNECTED");
  Blynk.syncVirtual(V0,V1);
  timer.setInterval(1000L,getData);
}

BLYNK_WRITE(V0) {tempObj = param.asInt();}
BLYNK_WRITE(V1) {tempSer = param.asInt();}

void updateServo(int temp){
  Serial.println("Moving servo!");
  servoAngle = map(temp, 5, 30, 0, 180);
  myservo.write(servoAngle);
}

void deepSleep(){
  Serial.println("Going to sleep.");
  Blynk.disconnect();
  ESP.deepSleep(SLEEP_LENGTH * 1000000, WAKE_RF_DEFAULT);
}

void getData() {
  Serial.println("Getting data.");
  Serial.print("Temperature in server: ");
  Serial.println(tempSer);

  Serial.print("Received temperature: ");
  Serial.println(tempObj);

  if (tempObj != tempSer){
    Serial.println("Updating temperature!");
    Blynk.virtualWrite(V1, tempObj);
    updateServo(tempSer);
    myservo.attach(servoPin, 500, 2700);
    updateServo(tempObj);
  }
 
  timer.setInterval(500L,deepSleep);
}

void setup()
{
  Serial.begin(115200);
  delay(100);
  BlynkEdgent.begin();
  
}

void loop() {
  BlynkEdgent.run();
  timer.run();
}

The idea is for the servo motor to only move when the value in the blynk app changes (the functionality of the code works properly, I think it is just the deep sleep and the timers that make it crash for some reason).

I have searched the forums back and forth to look for a similar problem to the one I’m running into with no luck so I hope anyone can give me a hand :smiley:.

I think your whole logic is flawed.
The device won’t know when the value in the Blynk app has changed, because the device will be disconnected from Blynk whilst it’s in Deep Sleep. It will only know about the widget change when it wakes-up again, reconnects to Blynk and executes BLYNK_CONNECTED() and Blynk.syncVirtual()

This will mean you have a lag of up to 10 seconds before your device knows about the widget change. In reality, waking-up and connect8ing to WiFi then Blynk, then executing this code will probably take 10 seconds (although you have a timer set to 500ms to initiate Deep Sleep, and another one set to 1000ms to take some readings, so the readings will never happen)
That will give you about a 1:1 seep/wake ratio, which will mean your battery won’t last long. Extending the sleep time will improve the battery life, but make the servo even less responsive to widget inputs.

I wouldn’t use the Edgent sketch for Deep Sleep. You could add the Blynk.Air functionality to a non-Edgent sketch using this functionality…

But you need to ensure that the device doesn’t go to sleep while an update is taking place, which can be tricky.

Also, you shouldn’t use timers in a Deep Sleep sketch. You don’t want to wait around, you just want to get on with the operations in the desired sequence then go to sleep again.

This topic is rather long, but you’ll read a lot about suing Blynk with Deep Sleep if you study it…

But, we come back to the same problem, which is that you cant use Deep Sleep if you want interactive control over a device via the app or web dashboard.

One other thing, when you post compiler error messages or serial output in future, do it with triple backticks at the beginning and end (the same as when you post code), not Blockquotes.

Pete.

BLYNK_WRITE is a function that called every time device receives an update of Virtual Pin value from the server or app. For example, if you add a slider and attach it to virtual pin number 3,

BLYNK_WRITE(V3)
{
  servo.write(param.asInt());
}

Will only move the servo when you move the slider.

And

Should be in the void setup.

Hey Pete, thank you for your reply. Very constructive as always. I know the device will only update when it wakes up from deep sleep, actually, the 10 seconds of sleep where only a placeholder for testing, the idea is to check every 10-15 minutes or so. The 500ms and 1000ms were actually tests too in order to determine the delays needed to succesfully connect and update the data from the server in the minimum possible time, which works with the current configuration as the “getData” function is run only after the device has connected to the blynk server (or at least that is my understanding).

I’m curious about why the Edgent sketch shouldn’t be used for Deep Sleep. Is this related to those timer problems and the Exception 28?
Also, why not use timers deep sleep? For the sake of battery saving?

Hello John, thanks for the reply. The idea behind my code is to use syncVirtual to get the last value stored in the server and then get the new one from the app widgets using BLYNK_WRITE that is called by syncVirtual. That way I can compare if those values changed and then move the servo. By using only the BLYNK_WRITE function, if the value changes white the board sleeps, the change is not updated when it wakes up, and by using only the syncVirtual with the BLYNK_WRITE code you proposed, the servo is powered (although not moved) every time the board reboots.

And why should the calls for deepSleep and getData be in setup? If I do it that way, not only I have to guess the connection time to call the getData function but I still get the Exception 28.

The sketch is far more complex that you need for deep sleep. If you study each of the Edgent tabs and understand exactly what is happening in each one of them then fine - go with Edgent if you still thing that this extra baggage is worth it for your project.

Did you read through the Beehive connected topic and look at the code in post #17? You’ll see that the majority of the code executes in a serial way in the void loop, which will only execute once per wake cycle. It’s efficient and easy to follow, and there is no confusion about when one time will execute versus another. Times are needed to call functions on a cyclical basis every x milliseconds, but you only want a function to execute once before going to sleep, so timers are redundant.

I think you misunderstand how the Blynk eco system works.
When a widget on either the mobile or web dashboard is used to update a datastream, that value is communicated back to the server, and the next time the device executes Blynk.run the server will notify the device of the new datastream value, by triggering the corresponding BLYNK_WRITE function.
Calling Blynk.suncVirtual(vPin) will cause the device to ask the server for the latest value for that virtual pin, once again triggering the relevant BLYNK_WRITE function.
Any widget value changes that occur when the device is sleeping will still be sent from the mobile/web dashboard, and be available to be requested by the device when it wakes-up and synchronises.

First of all, stop using Edgent.
You don’t have to guess any times if you eliminate timers and do one-off sequential processing of the code each time the device wakes.

Pete.

I tried putting my esp32 into deep sleep mode, and it worked perfectly with the Edgent sketch. so I can tell that the Edgent sketch and deep sleep mode can work together.

1 Like

I’ve ended up following the sequential approach, although it still fails in the edgent sketch (Exception 28, still haven’t found what causes it), so maybe deep sleep, edgent and esp8266 are not meant to work together haha.
It does work though if I use the blynk library instead of the edgent one and make a simpler version of the beehive code.
However, I have to note that the Blynk ecosystem does not work as you suggested in your post, at least not the BLYNK_WRITE function as it does require the syncVirtual call to sync with the server parameters, it will not request it by default when the device wakes up.

I didn’t say that it synchronised automatically on wake-up, what I said was…

You need to have a BLYNK_CONNECTED() function with Blynk.syncVirtual inside it to get these values back, and in that situation the sequential code should probably sit inside the BLYNK_CONNECTED() function to ensure that it executes AFTER getting the values from Blynk.

That is very different from what you were doing in your first post.

Pete.