8000 esp32: Writing data to flash files stalls everything including interrupts for 80ms · Issue #3782 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

esp32: Writing data to flash files stalls everything including interrupts for 80ms #3782

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
goatchurchprime opened this issue May 14, 2018 · 17 comments

Comments

@goatchurchprime
Copy link

I'm trying to log bursts of analog values to the internal flash file system on an ESP32 (at the period of 10ms).

Unfortunately for every 4096bytes of data everything freezes for about 80ms while spi_flash_write() executes.

fout = open("data.txt", "w")
for i in range(n):
    t = time.ticks_ms()
    fout.write("%08d" % t)
fout.close()

I've tried implementing the data collection and storage of values into ringbuffer at a 10ms period using machine.Timer(), but the problem was not solved. When the main loop retrieves these values and stores them into a file everything freezes for 80ms at the page boundary -- including those Timer() interrupts, which seem to get queued up and executed consecutively.

I think the problem is down to the function call spi_flash_write(), which is made here:

esp_err_t res = spi_flash_write(offset, bufinfo.buf, bufinfo.len);

The espressif documentation https://dl.espressif.com/doc/esp-idf/latest/api-reference/storage/spi_flash.html states:

Because the SPI flash is also used for firmware execution (via the instruction & data caches), these caches much be disabled while reading/writing/erasing. This means that both CPUs must be running code from IRAM and only reading data from DRAM while flash write operations occur... To avoid reading flash cache accidentally, when one CPU commences a flash write or erase operation the other CPU is put into a blocked state and all non-IRAM-safe interrupts are disabled on both CPUs, until the flash operation completes.

According to the memory layout not much can be run from the IRAM; our code is generally running from the IROM (flash memory), so all execution gets disabled during the write.

It's a long shot, but is there a version of spi_flash_write() that does not have this effect by assuming it's saving only data, not program code?

As far as I can know, the only time program code is written to flash is via the esptool.py, (Micropython files don't count as they are interpreted), so I don't see why the crazy assumption that spi_flash_write() would be writing stuff under the program counter?

The issue is a bit of a show-stopper in terms of making use of the copious on-board flash for datalogging purposes.

(The discussion began here https://forum.micropython.org/viewtopic.php?f=18&t=4725&p=27620#p27620 but I'm posting it as a ticket as it seems to relate to a fundamental firmware issue and application limitation.)

Potentially related tickets: Add accurate interrupt counter #3775, Benchmarking SDCard Write Speeds #2648

@adritium
Copy link

This seems like an issue with the operating system, not micropython. The definition for spi_flash_write() is not even contained in the uPy code.

@adritium
Copy link

According to the memory layout not much can be run from the IRAM;

Why don't you place your code in Internal ROM?

The Embedded Memory consists of four segments: internal ROM (448 KB), internal SRAM (520 KB), RTC FAST
memory (8 KB) and RTC SLOW memory (8 KB).
The 448 KB internal ROM is divided into two parts: Internal ROM 0 (384 KB) and Internal ROM 1 (64 KB). ...
Table 3 lists all embedded memories and their address ranges on the data and instruction buses.

@adritium
Copy link
adritium commented May 14, 2018

As far as I can know, the only time program code is written to flash is via the esptool.py, (Micropython files don't count as they are interpreted), so I don't see why the crazy assumption that spi_flash_write() would be writing stuff under the program counter?

const int array[] = {1, 2, 3, 4, 5};
void func(int n)
{
    int i;
    for(int i = 0; i < n; i++)
    {
        array[i] = something();
    }
}

array is constant data and you could be writing over it when you write the data section but you might be executing func() which uses this data section.

@adritium
Copy link
adritium commented May 15, 2018

Not an expert on flash memory but even on my micro, manual says

Read access to one data flash block possible while programming or erasing data in
another program flash block or data flash block

Which strongly implies you cannot simulatenously read and write to same flash "block".

So, unless you can move your code to IRAM and/or internal ROM, you're stuck.

@dpgeorge issue closed.

8000
@dpgeorge
Copy link
Member

I think the only way to make this work is to provide a function written in C that lives in IRAM (with data in DRAM) and does the reading of the ADC, storing it in a buffer (in DRAM) for Python code to read periodically and write to flash. For this to work, everything involved in the ISR and ADC reading must be in IRAM. It doesn't look like adc1_get_raw() (a function provided by the ESP IDF) is in IRAM so this approach looks impossible without heavy modifications of the ESP IDF itself.

Basically the architecture of the ESP32 means you cannot write to flash while also doing timing-critical things.

A way around this would be to provide a second external SPI flash (or even SD card) dedicated to storage, as writing to that wouldn't interfere with the running of the code.

@dpgeorge dpgeorge changed the title Writing data to flash files stalls everything including interrupts for 80ms esp32: Writing data to flash files stalls everything including interrupts for 80ms May 15, 2018
@goatchurchprime
Copy link
Author

So the recommendation is that the internal flash is not to be used for sub-1second data logging.

I wonder how to make this obscure fact known to anyone else who gets excited by the amount of on-board memory and tries it.

I've been trialing using an external micro-SD card on SPI using that pretty neat file mounting.

On an ESP8266 sing the code above saving to an SD file, most file writes take 2ms, except every 64th (512 bytes) takes 15ms, and every 256th file write (2048 bytes) takes 50ms.

When I use machine.Timer(0) with period=10 and save to the SD card, then the timings are spaced mostly between 6ms and 14ms, so we are not encountering the 80ms stall you get when using the internal flash.
https://github.com/goatchurchprime/jupyter_micropython_developer_notebooks/blob/master/FileSystemTests/SD_Card_Mount.ipynb

I get about the same when running an external SD card on an ESP32 -- 15ms and 50ms stalls when you run the writes inline with the data acquisition, but this time variations of only 1ms either side of 10ms every 2kbs of data when you do the data acquisition using an irq Timer.

So this works adequately, but it is annoying having to bolt on this external memory unit as a work-around. It would be much handier if espressif declared only half of its internal memory was to be used for executable code so that the other half could be used for data without having to worry about freezing everything for the program cache.

Is there a good forum for letting them know of application/issues like this

@dpgeorge
Copy link
Member

I wonder how to make this obscure fact known to anyone else who gets excited by the amount of on-board memory and tries it.

Ther are quite a few technical details about the ESP32 (and any chip in fact) which you can only really know once you start playing with it and seeing if it's suited to your particular application (see eg #2972). What is a show-stopper for some (eg logging to flash) may be no problem at all for others. But in microcontroller applications, I agree that you expect some level of real-time capability/behaviour.

It would be much handier if espressif declared only half of its internal memory was to be used for executable code so that the other half could be used for data without having to worry about freezing everything for the program cache.

That wouldn't help. The point is (as mentioned above) that it's a limitation of the SPI flash itself, that if you are erasing a page then the entire flash is stalled (can't read or write) until it's done erasing. So the CPU can't execute anything from the external memory when it's erasing.

Is there a good forum for letting them know of application/issues like this

Espressif have a forum and active Github account.

@adritium
Copy link

Is the internal ROM too small to put your entire code?

@robert-hh
Copy link
Contributor

The internal ROM is .. ROM, part of the silicon, changeable only by making new silicon.

@adritium
Copy link
adritium commented May 17, 2018

@robert-hh
That’s not what ROM means. Yes Internal ROM is part of the silicon but it’s shipped empty and you can place in it whatever you want to.

IROM (code executed from Flash)
If a function is not explicitly placed into IRAM or RTC memory, it is placed into flash.

https://dl.espressif.com/doc/esp-idf/latest/api-guides/general-notes.html#memory-layout

Code can be placed there during programming.
You can modify the linker script to place your code anywhere you want.
You can even split your code in multiple different flash blocks and the linker will insert the necessary jump instructions between flash blocks.
My micro has 2 discontinuous flash blocks and I had to place the /py code in a different flash block than my other code because of size issues.

@robert-hh
Copy link
Contributor

That's how I read the datasheet: The device has 448k of internal ROM, containing the bootloader and support library code. And is has external flash, which is either called IROM (for code and data) or DROM (data only). This flash is up to four devices with a SPI interface, size up to 16 MByte each. This flash memory (and external PsRAM) is mapped by the MMU into the address space of the CPU, and code in the internal ROM copies the required blocks from the external flash or RAM into the cache, whenever needed. That takes about 300µs per block and can be observed, e.g. as a jitter of ISR's. The total address space 8000 provided for external memory is about 20 MBytes.
So maybe we mean the same, but from a different perspective. It could be interesting if the flash is split into two chips, such that erasing at one chip does not affect the other one.
P.S.: There are variants of the ESP32 with internal flash. But these do not allow to add external flash.

@adritium
Copy link
adritium commented May 17, 2018

When you build the image, there should be a .map file artifact that states how much of each memory region is used up. Even if there’s something left in the onboard ROM, it’s possible it’s not big enough to be useful ie cannot hold a meaningful amount of the /py code.
In that case, you’ll need to store your code to IRAM but it’ll probably not be big enough so then the conclusion is that it’s the wrong micro/flash configuration for your uses: you should not run your code from the same flash block you’re (often) writing to.

@robert-hh
Copy link
Contributor
robert-hh commented May 17, 2018

If I look into the map file of a ESP32 build, the memory regions used are those of internal RAM and external flash, which matches the settings in esp_out-ld . It shows no use of internal ROM.

Memory Configuration

Name             Origin             Length             Attributes
iram0_0_seg      0x0000000040080000 0x0000000000020000 xr
iram0_2_seg      0x00000000400d0018 0x000000000032ffe8 xr
dram0_0_seg      0x000000003ffb0000 0x000000000002c200 rw
drom0_0_seg      0x000000003f400018 0x00000000003fffe8 r
rtc_iram_seg     0x00000000400c0000 0x0000000000002000 xrw
rtc_slow_seg     0x00000000500007f8 0x0000000000000808 rw
*default*        0x0000000000000000 0xffffffffffffffff

Do you have the experience that the internal ROM area is writable by the flash loader?

@adritium
Copy link
adritium commented May 17, 2018

Do you have the experience that the internal ROM area is writable by the flash loader?

If you mean on the ESP32, not I do not.
If you mean in general on microcontrollers, yes I do.

It is possible your ESP32 build tools disallow you from writing to the IROM because that's where the OS is placed . . . and I think I found the doc that confirms it: IROM (code executed from Flash):

ESP-IDF places the code which should be executed from flash starting from the beginning of 0x400D0000 — 0x40400000

and if you look at the technical manual, that range is in the external memory section.

Also, IROM (from the webpage) isn't the same thing as internal ROM (from the manual): IROM refers to external flash that is read via SPI (or something) while internal ROM is the area YOU would like to be able to save your code to so you don't have debilitating 80ms freeze when you write to the SPI flash.

They really don't want you to touch the internal ROM

While the ESP32 does indeed have a ROM built in, for our purposes, we can ignore it.

@robert-hh
Copy link
Contributor

Yes, I totally agree. So we talked about the same thing, just looking at it differently.

@adritium
Copy link
adritium commented May 17, 2018

@goatchurchprime or @dpgeorge you may close this issue.

Summary
If you write data to any flash block, you cannot read from it for the duration of the write which includes reading code to execute resulting in a "freeze".
If the freeze duration is unacceptable, you must get a second flash block and separate the code from the "filesystem".
Saving your code to the internal ROM as defined by the reference manual appears to be impossible (or at the very least highly discouraged).
None of this has anything to do with micropython but with how flash memory works and how ESP32 manufacturer has chosen to limit access to the internal ROM.

@dpgeorge
Copy link
Member

Theres's not much that can be done about this from the MicroPython side, so closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants
0