system: add an example showing how to run non-iram ISR when operating Flash

pull/8284/head
Armando 2022-01-05 17:05:57 +08:00
rodzic 1d02e638bd
commit a36e5188e9
10 zmienionych plików z 323 dodań i 0 usunięć

Wyświetl plik

@ -54,6 +54,19 @@ example_test_pytest_esp32c3_generic:
- ESP32C3
- Example_GENERIC
example_test_pytest_esp32c3_flash_suspend:
extends:
- .pytest_examples_dir_template
- .rules:test:example_test-esp32c3
needs:
- build_examples_pytest_esp32c3
variables:
TARGET: esp32c3
ENV_MARKER: flash_suspend
tags:
- ESP32C3_IDF
- UT_T1_Flash_Suspend
# for parallel jobs, CI_JOB_NAME will be "job_name index/total" (for example, "IT_001 1/2")
# we need to convert to pattern "job_name_index.yml"
.define_config_file_name: &define_config_file_name |

Wyświetl plik

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(flash_suspend)

Wyświetl plik

@ -0,0 +1,115 @@
| Supported Targets | ESP32-C3 |
| ----------------- | -------- |
# Flash Suspend Example
## Overview
One problem in IDF is that during Flash operations (via either ESP Flash, NVS or Partition APIs), concurrent execution of code residing in Flash will lead to a crash. Consequently, all ISRs and their callbacks need to be placed in internal RAM.
Background: In IDF, executable code (instructions) are usually placed in Flash.
However, by using the auto-suspend feature, it is not necessary to put ISRs and their callbacks into internal RAM anymore.
Here we define two sets of Flash operations:
SET1: Operations where CPU fetches data and instructions from Flash.
SET2: `ESP Flash` driver operations and other operations from drivers based on `ESP Flash` (NVS, Partition drivers, etc.).
- During Erase Operation (SET 2), if there is a SET 1 request, the ongoing SET 2 Flash operation will be suspended automatically, the Erase Operation is interrupted, CPU will get a chance to read from Flash. After CPU's access, the Flash chip will be resumed automatically and the Erase Operation will continue.
- During other Flash Operations (SET 2), if there is a SET 1 request, CPU will usually wait until the SET 2 operation finishes, then get the chance to read from Flash.
## Example Process
As mentioned above, in this example we go through the following steps:
`General Steps`:
1. Create a partition for Flash Erase Operation
2. Configure a GP timer to trigger an alarm once
3. Register a timer alarm event callback
`Misc Steps`:
4. Make the callback call a function (`s_function_in_flash`) in Flash
5. Start the timer
6. Start a Flash Erase Operation
7. Pause the timer
`IRAM Steps`:
8. Make the callback call a function (`s_function_in_iram`) in Internal RAM
9. Start the timer
10. Start a Flash Erase Operation
## Example Result
You will see an ISR happening during each of the Flash Erase Operations (SET 2).
### `Misc Steps`
The ISR callback calls a function, and its main body is in `.flash.text`. You may use
```
riscv32-esp-elf-objdump
```
to see the concrete instructions of the function. It contains around 10 instructions. The Cache will read from the Flash, if these instructions are not cached yet. CPU then fetches these instructions from the Cache. For ESP32C3, the time between the call to `s_function_in_flash`, and its execution, is around 32 us, when the CPU frequency is 160MHz. Apart from the context switch time, instruction execution time (please refer to the example comments to know the function body), and other hardware overheads, there is still around 28.5us, which should be the time for the Cache synchronisation (see note 1.).
----
*note 1*
**Cache synchronisation:**
In ESP-IDF, the cache is a small memory system which can provide low-latency access to a block of data, residing in Flash or PSRAM. The CPU accesses Flash and PSRAM via the cache. When the CPU requested data that is not in the cache, we have a cache miss and the data will be synchronized to cache from either Flash or PSRAM.
In this example, for `Misc Steps`, the involved cache synchronization time is a bit longer, since a SET 2 Flash Erase Operation is ongoing. The ESP chip will send a suspend command to the Flash chip, to suspend the ongoing Erase Operation, and let the Cache access the Flash chip.
*The Cache synchronisation time would be even longer, if more instructions need to be fetched.*
### `IRAM Steps`
The ISR callback calls a function, and its main body is in `.iram0.text`. For ESP32C3, the time between `s_function_in_iram` is called, and its execution, is around 3.8 us, when the CPU frequency is 160MHz. Under this circumstance, around 400 times instruction execution will cost about 2.5us. Apart from the context switch and other overheads, these instructions in Internal RAM can be fetched within 1us.
## Conclusion for Code Placement
For ESP-IDF drivers, we usually expect the ISR callbacks to be short, simple and useful. It's more like a way to inform users of a hardware event. And the event handling routines should be delegated to the main program.
What should not be used in an ISR callback:
- printf (there is mutex underneath)
- long log print (even using `esp_rom_printf`)
- blocking logic
Based on these principles,
- If the `CONFIG_SPI_FLASH_AUTO_SUSPEND` is not enabled/supported, usually an ISR and its callbacks should be put in Internal RAM, when you also need to operate the Flash via `ESP Flash` driver and the drivers based on `ESP Flash`.
- If the `CONFIG_SPI_FLASH_AUTO_SUSPEND` is enabled, the ISR function and its callbacks **can** be put in Flash. But their maximum response time will be **prolonged**.
- If the `CONFIG_SPI_FLASH_AUTO_SUSPEND` is enabled, a Flash Erase Operation would be interrupted by the CPU / Cache access request to the Flash. Therefore, when the ISR happens frequently, the Flash Erase Operation may be **prolonged** a lot.
### Configure the project
To change the `Auto suspend long erase/write operations` (`CONFIG_SPI_FLASH_AUTO_SUSPEND`), open the project configuration menu (`idf.py menuconfig`).
Navigate to `Component config -> SPI Flash driver` menu.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build and flash the project..
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
I (285) Example: found partition 'storage1' at offset 0x110000 with size 0x80000
I (5615) Example: Flash Driver Erase Operation finishes, duration:
2635600.01 us
I (5615) Example: During Erase, ISR callback function(in flash) response time:
32.26 us
I (8295) Example: Flash Driver Erase Operation finishes, duration:
2626830.69 us
I (8295) Example: During Erase, ISR callback function(in iram) response time:
3.83 us
I (8295) Example: Finish
```
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

Wyświetl plik

@ -0,0 +1,2 @@
idf_component_register(SRCS "app_main.c"
INCLUDE_DIRS "")

Wyświetl plik

@ -0,0 +1,153 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* In this example, we show you a way to make an ISR-based callback work during Flash operations, when the ISR-based
* callback is put in Flash.
*
* Please read the README.md to know more details about this feature!
*/
#include <stdbool.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "esp_cpu.h"
#include "esp_partition.h"
#include "driver/gptimer.h"
#define TIMER_RESOLUTION_HZ (1 * 1000 * 1000) // 1MHz resolution
#define TIMER_ALARM_PERIOD_S 1 // Alarm period 1s
#if CONFIG_IDF_TARGET_ESP32C3
#define CPU_FREQ_MHZ CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ
#endif
#define RECORD_TIME_PREPARE() uint32_t __t1, __t2
#define RECORD_TIME_START() do {__t1 = esp_cpu_get_ccount();} while(0)
#define RECORD_TIME_END(p_time) do{__t2 = esp_cpu_get_ccount(); p_time = (__t2 - __t1);} while(0)
#define GET_US_BY_CCOUNT(t) ((double)(t)/CPU_FREQ_MHZ)
const static char *TAG = "Example";
DRAM_ATTR static uint32_t s_t1;
DRAM_ATTR static uint32_t s_flash_func_t2;
DRAM_ATTR static uint32_t s_iram_func_t2;
static NOINLINE_ATTR void s_function_in_flash(void)
{
/**
* - Here we will have few instructions in .flash.text
* - Cache will read from Flash to get the instructions synchronized.
* - CPU will execute around 400 times.
*/
for (int i = 0; i < 100; i++) {
asm volatile("nop");
}
s_flash_func_t2 = esp_cpu_get_ccount();
}
static IRAM_ATTR NOINLINE_ATTR void s_funtion_in_iram(void)
{
/**
* - Here we will have few instructions in .iram0.text
* - CPU will execute around 400 times.
*/
for (int i = 0; i < 100; i++) {
asm volatile("nop");
}
s_iram_func_t2 = esp_cpu_get_ccount();
}
static bool IRAM_ATTR on_gptimer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
bool is_flash = *(bool *)user_ctx;
s_t1 = esp_cpu_get_ccount();
if (is_flash) {
s_function_in_flash();
} else {
s_funtion_in_iram();
}
return false;
}
static const esp_partition_t *s_get_partition(void)
{
//Find the "storage1" partition defined in `partitions.csv`
const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage1");
if (!result) {
ESP_LOGE(TAG, "Can't find the partition, please define it correctly in `partitions.csv`");
abort();
}
return result;
}
void app_main(void)
{
//Get the partition used for SPI1 erase operation
const esp_partition_t *part = s_get_partition();
ESP_LOGI(TAG, "found partition '%s' at offset 0x%x with size 0x%x", part->label, part->address, part->size);
//Erase whole region
ESP_ERROR_CHECK(esp_flash_erase_region(part->flash_chip, part->address, part->size));
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_APB,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = TIMER_RESOLUTION_HZ,
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = 1 * 1000 * 1000,
.flags.auto_reload_on_alarm = false,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
gptimer_event_callbacks_t cbs = {
.on_alarm = on_gptimer_alarm_cb,
};
bool is_flash = true;
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, &is_flash));
ESP_ERROR_CHECK(gptimer_start(gptimer));
uint32_t erase_time = 0;
RECORD_TIME_PREPARE();
RECORD_TIME_START();
ESP_ERROR_CHECK(esp_flash_erase_region(part->flash_chip, part->address, part->size));
RECORD_TIME_END(erase_time);
ESP_ERROR_CHECK(gptimer_stop(gptimer));
ESP_LOGI(TAG, "Flash Driver Erase Operation finishes, duration:\n\t\t%0.2f us", GET_US_BY_CCOUNT(erase_time));
ESP_LOGI(TAG, "During Erase, ISR callback function(in flash) response time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_flash_func_t2 - s_t1));
//Let the timer alarm callback to run code reside in .iram0.text
is_flash = false;
ESP_ERROR_CHECK(gptimer_set_raw_count(gptimer, 0));
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
ESP_ERROR_CHECK(gptimer_start(gptimer));
RECORD_TIME_START();
//Erase whole region
ESP_ERROR_CHECK(esp_flash_erase_region(part->flash_chip, part->address, part->size));
RECORD_TIME_END(erase_time);
ESP_ERROR_CHECK(gptimer_stop(gptimer));
ESP_LOGI(TAG, "Flash Driver Erase Operation finishes, duration:\n\t\t%0.2f us", GET_US_BY_CCOUNT(erase_time));
ESP_LOGI(TAG, "During Erase, ISR callback function(in iram) response time:\n\t\t%0.2f us", GET_US_BY_CCOUNT(s_iram_func_t2 - s_t1));
ESP_LOGI(TAG, "Finish");
ESP_ERROR_CHECK(gptimer_del_timer(gptimer));
}

Wyświetl plik

@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage1, data, fat, , 512K,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage1, data, fat, , 512K,

Wyświetl plik

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded.dut import Dut
@pytest.mark.esp32c3
@pytest.mark.flash_suspend
@pytest.mark.parametrize('config', [
'flash_suspend',
], indirect=True)
def test_flash_suspend_example(dut: Dut) -> None:
dut.expect_exact('found partition')
res = dut.expect(r'During Erase, ISR callback function\(in flash\) response time:\s+(\d+(\.\d{1,2})) us')
response_time = res.group(1).decode('utf8')
assert 0 <= float(response_time) < 40
res = dut.expect(r'During Erase, ISR callback function\(in iram\) response time:\s+(\d+(\.\d{1,2})) us')
response_time = res.group(1).decode('utf8')
assert 0 <= float(response_time) < 5

Wyświetl plik

@ -0,0 +1,4 @@
CONFIG_SPI_FLASH_AUTO_SUSPEND=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"

Wyświetl plik

@ -0,0 +1,2 @@
CONFIG_ESP32C3_DEFAULT_CPU_FREQ_160=y
CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ=160

Wyświetl plik

@ -13,6 +13,7 @@ markers =
esp32s3: support esp32s3 target
esp32c3: support esp32c3 target
generic: tests should be run on generic runners
flash_suspend: support flash suspend feature
# log related
log_auto_indent = True