Hi Everyone,
I'm trying to write a brownout handler routine for my commercial project (using ESP32-WROVER IE) and I'm wondering if anyone can help me figure out how to do this. I am using internal SPIFAT, NVS, and SD FAT. I also have a second MCU on the board that is relying on the same supply being up for a similar routine.
I've written an intermediate layer on all my file IOs so that I can prevent all my code from accessing the files after the brownout is triggered. When brownout is triggered by the external comparator they should only finish the current operation.
The idea is that the circuit has enough bulk energy storage to finish any pending operations and exit cleanly. When everything goes as expected my GPIO brownout task drops clock speeds, closes files etc and I get >800ms of time (I need ~400ms). The problem is that flash operations on the internal SPI chip are slow and block all non IRAM code, including the brownout task. I've been through the menuconfig and setup the options to lower the latency/duration on the internal flash ops as much as possible already (yield during erase, and force sector erase etc.). The delay in processing the brownout uses too much power.
I want to do the following:
GPIO ISR handler in IRAM on Brownout input:
Immediately drop CPU speed and disable wifi. Don't care about clean deinit, just power consumption.
Very high priority brownout task:
Close open files etc and then go to sleep until hardware supervisor resets circuit.
I'm stuck on not understanding the low level details of how to reduce the power consumption of the wifi from that ISR in IRAM.
esp_wifi_stop() takes a long time to complete I think and also not sure if it is ISR or IRAM safe.
Should I be looking at lower layers of code? Should I be writing to registers to force things to disable and can I kill the WiFi task?
Any ideas or lessons from someone else doing the same thing would be appreciated.
Thanks.
How to disable WiFi from GPIO ISR for Brownout?
-
- Posts: 41
- Joined: Thu Feb 13, 2020 1:35 am
-
- Posts: 9746
- Joined: Thu Nov 26, 2015 4:08 am
Re: How to disable WiFi from GPIO ISR for Brownout?
There's very few 'big' things you can safely do from an ISR, and I don't think stopping WiFi is one of them. As you presumably already have that high-prio task that gets unblocked from the ISR, it's better to put that call in there. I understand the issue with waiting for a flash write to finish, but I'm not sure if there's a proper solution to that.
-
- Posts: 41
- Joined: Thu Feb 13, 2020 1:35 am
Re: How to disable WiFi from GPIO ISR for Brownout?
Thanks for your input ESP_Sprite,
Still keen to find a way if anyone else knows of anything.
I'm going to update a little bit about what I have found here in case someone ends up following in my footsteps later on.
I'm using to turn off the wifi now in my high priority task. It seems quicker than esp_wifi_stop.
Also, with regard to CONFIG_SPI_FLASH_ERASE_YIELD_TICKS, it is in the docs but I missed the fact that this is a yield on a per-task basis. That means that if two tasks are erasing the flash they will interleave and can do a flash operation during the yield time of the other task. This is great for keeping up erase performance, but it does mean that if your yield time is less than your erase time you can end up totally locked up again and your two erase tasks can use all of the CPU cycles. I'm going to have to look at a mutex or something here to prevent simultaneous operations. Ideally it would be great to have a menuconfig option to make the yield global via a mutex so that you could be assured that a minimum number of ticks would be free every sector erase. If you had two tasks, all the normal priority mechanisms would still work as well to determine who had priority.
I've attached a snapshot of my test setup. I have the logic analyzer on a GPIO pin. A high priority task is pulsing the pin every millisecond. Two competing tasks with lower priority are both trying to erase flash at the same time. One is accessing NVS, the other SPIFAT. NOTE: I am reading between the lines here a little bit as I don't actually have an indication of which task is running.
EDIT: These options are from my sdkconfig file for the attached test:
Still keen to find a way if anyone else knows of anything.
I'm going to update a little bit about what I have found here in case someone ends up following in my footsteps later on.
I'm using
Code: Select all
esp_wifi_set_mode(WIFI_MODE_NULL);
Also, with regard to CONFIG_SPI_FLASH_ERASE_YIELD_TICKS, it is in the docs but I missed the fact that this is a yield on a per-task basis. That means that if two tasks are erasing the flash they will interleave and can do a flash operation during the yield time of the other task. This is great for keeping up erase performance, but it does mean that if your yield time is less than your erase time you can end up totally locked up again and your two erase tasks can use all of the CPU cycles. I'm going to have to look at a mutex or something here to prevent simultaneous operations. Ideally it would be great to have a menuconfig option to make the yield global via a mutex so that you could be assured that a minimum number of ticks would be free every sector erase. If you had two tasks, all the normal priority mechanisms would still work as well to determine who had priority.
I've attached a snapshot of my test setup. I have the logic analyzer on a GPIO pin. A high priority task is pulsing the pin every millisecond. Two competing tasks with lower priority are both trying to erase flash at the same time. One is accessing NVS, the other SPIFAT. NOTE: I am reading between the lines here a little bit as I don't actually have an indication of which task is running.
EDIT: These options are from my sdkconfig file for the attached test:
Code: Select all
CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE=y
CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y
CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=5
CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=100
CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=512
- Attachments
-
- Flash erase yield behaviour.png (18.66 KiB) Viewed 2341 times
-
- Posts: 41
- Joined: Thu Feb 13, 2020 1:35 am
Re: How to disable WiFi from GPIO ISR for Brownout?
So after going through the SPI driver I have come to the conclusion that the scenario above (multiple tasks pending on flash operations) wasn't really considered when it was written.
I've come up with my own derivative implementation of the yield function. (Original Espressif version here: https://github.com/espressif/esp-idf/bl ... func_app.c)
I'll attach some snippets here with my changes for reference, but I'm somewhat out of my comfort zone so evaluate before using for your own purposes. I've used critical sections to protect the state tracking variables for the yield timer. I'm not sure if that is the correct solution. So far it seems to help, and I don't get multiple erase operations without a yield period even with multiple tasks. If anyone else comes up with something better, or can spot an issue, please let me know!
Here are my changes:
I have added a spinlock and an acquired state tracking variable to the spi arguments:
I have changed the Yield function to re-asses if another yield is required after the yield finishes:
And changed the yield check function to track the acquired/released state
I've come up with my own derivative implementation of the yield function. (Original Espressif version here: https://github.com/espressif/esp-idf/bl ... func_app.c)
I'll attach some snippets here with my changes for reference, but I'm somewhat out of my comfort zone so evaluate before using for your own purposes. I've used critical sections to protect the state tracking variables for the yield timer. I'm not sure if that is the correct solution. So far it seems to help, and I don't get multiple erase operations without a yield period even with multiple tasks. If anyone else comes up with something better, or can spot an issue, please let me know!
Here are my changes:
I have added a spinlock and an acquired state tracking variable to the spi arguments:
Code: Select all
typedef struct {
app_func_arg_t common_arg; //shared args, must be the first item
bool no_protect; //to decide whether to check protected region (for the main chip) or not.
uint32_t acquired_since_us; // Time since last explicit yield()
uint32_t released_since_us; // Time since last end() (implicit yield)
uint32_t acquired; // Track the acquired/released state
} spi1_app_func_arg_t;
static portMUX_TYPE DRAM_ATTR yield_spinlock = portMUX_INITIALIZER_UNLOCKED; // Spin lock to make accessing spi1 args threadsafe.
Code: Select all
static IRAM_ATTR esp_err_t spi1_flash_os_yield(void *arg, uint32_t* out_status)
{
spi1_app_func_arg_t * ctx = (spi1_app_func_arg_t *)arg;
if (likely(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)) {
#ifdef CONFIG_SPI_FLASH_ERASE_YIELD_TICKS
do{
int32_t delay;
uint32_t time = esp_system_get_time();
// Spin lock may not be required as reading U32 should be atomic.
// Will get issues if not aligned correctly etc. I think
portENTER_CRITICAL_ISR(&yield_spinlock);
delay = (time - ctx->released_since_us);
portEXIT_CRITICAL_ISR(&yield_spinlock);
// Work out the remaining delay time.
delay = (CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS) - (delay/1000);
// Limit it
if(delay > CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS){
delay = CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS;
}
else if (delay <= 0){
delay = 1;
}
vTaskDelay(delay * portTICK_PERIOD_MS);
}while(on_spi1_check_yield(ctx)); // Check again to make sure another yield is not required.
#else
vTaskDelay(1);
#endif
}
on_spi1_yielded(ctx);
return ESP_OK;
}
Code: Select all
static inline IRAM_ATTR bool on_spi1_check_yield(spi1_app_func_arg_t* ctx)
{
#ifdef CONFIG_SPI_FLASH_YIELD_DURING_ERASE
uint32_t time = esp_system_get_time();
bool ret = true;
portENTER_CRITICAL_ISR(&yield_spinlock);
if(ctx->acquired){
// SPI has been acquired by a task already for flash write/erase ops.
if ((time - ctx->acquired_since_us) >= (CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS * 1000)) {
// Bus has been acquired for too long and it is time to release the bus.
ctx->acquired = 0;
ctx->released_since_us = time;
}
}
else if ((time - ctx->released_since_us) >= (CONFIG_SPI_FLASH_ERASE_YIELD_TICKS * portTICK_PERIOD_MS * 1000)) {
// SPI is available and enough time has passed to allow us to perform the next write/erase operation.
ctx->acquired = 1; // Set acquired flag
ctx->acquired_since_us = time;
ret = false; // Don't need to yield. Bus will be acquired by caller.
}
portEXIT_CRITICAL_ISR(&yield_spinlock);
return ret;
#else
return false;
#endif
}
static inline IRAM_ATTR void on_spi1_released(spi1_app_func_arg_t* ctx)
{
#ifdef CONFIG_SPI_FLASH_YIELD_DURING_ERASE
uint32_t time = esp_system_get_time();
portENTER_CRITICAL_ISR(&yield_spinlock);
if(ctx->acquired){
// If the operation was a long operation but only called the yield function once It may still need a yield at the next operation.
if ((time - ctx->acquired_since_us) >= (CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS * 1000)) {
// Bus is released now. The next task needs to know when this one finished.
// NOTE: Resetting this every time it is released will make all writes/erases,
// even short ones, trigger a full yield.
ctx->released_since_us = time;
}
ctx->acquired = 0;
}
portEXIT_CRITICAL_ISR(&yield_spinlock);
#endif
}
static inline IRAM_ATTR void on_spi1_acquired(spi1_app_func_arg_t* ctx)
{
// Ideally, when the time after `on_spi1_released()` before this function is called is larger
// than CONFIG_SPI_FLASH_ERASE_YIELD_TICKS, the acquired time should be reset. We assume the
// time after `on_spi1_check_yield()` before this function is so short that we can do the reset
// in that function instead.
}
static inline IRAM_ATTR void on_spi1_yielded(spi1_app_func_arg_t* ctx)
{
// Not needed anymore. on_spi1_check_yield() is now called from the spi1_flash_os_yield() function above.
}
-
- Posts: 41
- Joined: Thu Feb 13, 2020 1:35 am
Re: How to disable WiFi from GPIO ISR for Brownout?
I've done a lot of work on this in the last week and I've come to the conclusion that I can't rely on any of the normal methods for disabling WiFi even from a high priority task rather than the ISR. Unless I am missing something obvious...
The reason for this is they all seem to pend on synchronization objects of some kind or another and will block the task for a while in some situations. Sometimes it works, and sometimes I get everything corrupted.
For one test case I was seeing anywhere from 330ms and 700ms of brownout time. I was trying to use every combination I could think of using the following functions but they all seem to block at some point.
Against my better judgement, I'm now using this. It is super consistent and I get a nice 590ms +/- maybe 20ms.
However, this has to be included from the private header (esp_private/wifi.h). That same header carries the following disclaimer:
Is this a bad idea? I can't even find source code for this. I assume it's part of the precompiled WiFi blob? Does anyone know who could confirm my usage scenario of this?
The reason for this is they all seem to pend on synchronization objects of some kind or another and will block the task for a while in some situations. Sometimes it works, and sometimes I get everything corrupted.
For one test case I was seeing anywhere from 330ms and 700ms of brownout time. I was trying to use every combination I could think of using the following functions but they all seem to block at some point.
Code: Select all
esp_wifi_disconnect();
esp_wifi_set_mode(WIFI_MODE_NULL);
esp_wifi_stop();
esp_wifi_deinit();
Code: Select all
esp_wifi_power_domain_off();
Code: Select all
/*
* All the APIs declared here are internal only APIs, it can only be used by
* espressif internal modules, such as SSC, LWIP, TCPIP adapter etc, espressif
* customers are not recommended to use them.
*
* If someone really want to use specified APIs declared in here, please contact
* espressif AE/developer to make sure you know the limitations or risk of
* the API, otherwise you may get unexpected behavior!!!
*
*/
Who is online
Users browsing this forum: MicroController and 72 guests