Skip to content
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

Speeding up boot time to be comparable with C++ PIO #223

Closed
markjfisher opened this issue Jul 19, 2024 · 15 comments
Closed

Speeding up boot time to be comparable with C++ PIO #223

markjfisher opened this issue Jul 19, 2024 · 15 comments

Comments

@markjfisher
Copy link

Motivations

The project I'm involved with have a current ESP32 application written in C++ on PlatformIO that starts up in ~150ms.
For rust, the simple hello_world app is taking 400ms.

The target hosts the devices are connected to are 8 bit machines (atari, c64, coco, etc), and are used as boot drives in some scenarios.

The machines usually come up in ~200ms, so an esp32, powered by the host is already booted and available.

For rust, the 400ms startup would be too long, and immediately makes using it non-starter.

I'm looking for a way to understand if it's possible to have a custom application in rust start up much faster than the hello world template does currently. Not necessarily that the hello world app should start faster, but knowledge on how I can improve startup for an application in rust.

Solution

I want the knowledge/docs to be able to understand and write an application that boots in rust with minimal overhead to try and achieve the same startup times as C++ platformio targets.

Additional context

One area we noticed that speeds up the boot time was which filesystem is used for storage. We switched from SPIFFS to LittleFS and gained several hundred ms, so I'm wondering if there are similar elements to rust startup in template application.

Here is the startup log for the hello world up to the point the application starts

$ cargo run --release
    Finished release [optimized] target(s) in 0.06s
     Running `espflash flash --monitor target/xtensa-esp32-espidf/release/fujinet-hal-rust`
[2024-06-29T19:55:19Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-06-29T19:55:19Z INFO ] Connecting...
[2024-06-29T19:55:20Z INFO ] Using flash stub
Chip type:         esp32 (revision v1.0)
Crystal frequency: 40 MHz
Flash size:        16MB
Features:          WiFi, BT, Dual Core, 240MHz, Coding Scheme None
MAC address:       b8:f0:09:89:d1:c0
App/part. size:    380,656/4,128,768 bytes, 9.22%
[2024-06-29T19:55:22Z INFO ] Segment at address '0x1000' has not changed, skipping write
[2024-06-29T19:55:22Z INFO ] Segment at address '0x8000' has not changed, skipping write
[00:00:23] [========================================]     222/222     0x10000                                                                                                                                   [2024-06-29T19:55:46Z INFO ] Flashing has completed!
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371 
ets Jun  8 2016 00:22:57

rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:7104
load:0x40078000,len:15576
load:0x40080400,len:4
0x40080400 - _invalid_pc_placeholder
    at ??:??
ho 8 tail 4 room 4
load:0x40080404,len:3876
entry 0x4008064c
I (30) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (31) boot: compile time Jun  7 2023 07:48:23
I (33) boot: Multicore bootloader
I (37) boot: chip revision: v1.0
I (41) boot.esp32: SPI Speed      : 40MHz
I (45) boot.esp32: SPI Mode       : DIO
I (50) boot.esp32: SPI Flash Size : 4MB
I (54) boot: Enabling RNG early entropy source...
I (60) boot: Partition Table:
I (63) boot: ## Label            Usage          Type ST Offset   Length
I (71) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (78) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (86) boot:  2 factory          factory app      00 00 00010000 003f0000
I (93) boot: End of partition table
I (97) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=11778h ( 71544) map
I (132) esp_image: segment 1: paddr=000217a0 vaddr=3ffb0000 size=02138h (  8504) load
I (135) esp_image: segment 2: paddr=000238e0 vaddr=40080000 size=0bd6ch ( 48492) load
I (157) esp_image: segment 3: paddr=0002f654 vaddr=00000000 size=009c4h (  2500) 
I (159) esp_image: segment 4: paddr=00030020 vaddr=400d0020 size=3cea0h (249504) map
I (259) boot: Loaded app from partition at offset 0x10000
I (259) boot: Disabling RNG early entropy source...
I (271) cpu_start: Multicore app
I (275) cpu_start: Pro cpu start user code
I (275) cpu_start: cpu freq: 160000000 Hz
I (275) cpu_start: Application information:
I (280) cpu_start: Project name:     libespidf
I (285) cpu_start: App version:      1
I (289) cpu_start: Compile time:     Jun 29 2024 20:49:01
I (296) cpu_start: ELF file SHA256:  000000000...
I (301) cpu_start: ESP-IDF:          v5.2.2
I (306) cpu_start: Min chip rev:     v0.0
I (310) cpu_start: Max chip rev:     v3.99 
I (315) cpu_start: Chip rev:         v1.0
I (320) heap_init: Initializing. RAM available for dynamic allocation:
I (327) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (333) heap_init: At 3FFB2A70 len 0002D590 (181 KiB): DRAM
I (339) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (346) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (352) heap_init: At 4008BD6C len 00014294 (80 KiB): IRAM
I (360) spi_flash: detected chip: gd
I (363) spi_flash: flash io: dio
W (367) spi_flash: Detected size(16384k) larger than the size in the binary image header(4096k). Using the size in the binary image header.
W (380) pcnt(legacy): legacy driver is deprecated, please migrate to `driver/pulse_cnt.h`
W (389) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (398) main_task: Started on CPU0
I (402) main_task: Calling app_main()

Here's the log from the platformio c++ application:

09:37:33.920 > FujiNet v1.3 2024-04-26 06:08:28 (IEC) Started @ 10
09:37:33.921 > Starting heap: 4346100
09:37:33.921 > [src/main.cpp:135] main_setup(): Heap: 200795
09:37:33.922 >
09:37:33.938 > Detected Hardware Version: Nugget
09:37:33.939 > NO_BUTTONS: disabled all buttons
09:37:33.967 > LittleFS mounted.
09:37:34.047 > SD mounted.
09:37:34.047 > fnConfig::load
09:37:34.047 > Load fnconfig.ini from SD
09:37:34.047 > fopen = /fnconfig.ini : ok
09:37:34.051 > fnConfig::load read 1167 bytes from config file
09:37:34.052 > Reading wifi section
09:37:34.052 > Reading stored wifi section for index: 0
09:37:34.060 > FLASH Config Storage: Enabled
09:37:34.064 > fnConfig::load read 1167 bytes from FLASH config file
09:37:34.064 > IEC systemBus::setup()
09:37:34.065 > iecFuji::setup()
09:37:34.066 > Creating a default printer using FS_SDFAT storage and type 0
09:37:34.066 > Printer Device #04 Ready!
09:37:34.067 > Disk Device #08 Ready!
09:37:34.068 > Disk Device #09 Ready!
09:37:34.069 > Disk Device #10 Ready!
09:37:34.069 > Disk Device #11 Ready!
09:37:34.069 > Disk Device #12 Ready!
09:37:34.070 > Disk Device #13 Ready!
09:37:34.071 > Disk Device #14 Ready!
09:37:34.071 > Disk Device #15 Ready!
09:37:34.071 > Network Device #16 Ready!
09:37:34.072 > CPM Device #20 Ready!
09:37:34.073 > Clock Device #29 Ready!
09:37:34.073 > FujiNet Device #30 Ready!
09:37:34.073 > Available heap: 4248320
09:37:34.074 > Setup complete @ 162 (152ms)
@ivmarkov
Copy link
Collaborator

Multiple comments:

  1. In general, Rust is as fast as C, so a program written in Rust should start as quickly as a one written in C (with ESP IDF). Also, the esp-idf-* crates are just simple wrappers around C ESP IDF code, so whatever delays you are experiencing, they are likely in the ESP IDF itself, and not really related to Rust.

  2. In these logs:

I (31) boot: compile time Jun  7 2023 07:48:23
I (33) boot: Multicore bootloader
I (37) boot: chip revision: v1.0
I (41) boot.esp32: SPI Speed      : 40MHz
I (45) boot.esp32: SPI Mode       : DIO
I (50) boot.esp32: SPI Flash Size : 4MB
I (54) boot: Enabling RNG early entropy source...
I (60) boot: Partition Table:
I (63) boot: ## Label            Usage          Type ST Offset   Length
I (71) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (78) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (86) boot:  2 factory          factory app      00 00 00010000 003f0000
I (93) boot: End of partition table
I (97) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=11778h ( 71544) map
I (132) esp_image: segment 1: paddr=000217a0 vaddr=3ffb0000 size=02138h (  8504) load
I (135) esp_image: segment 2: paddr=000238e0 vaddr=40080000 size=0bd6ch ( 48492) load
I (157) esp_image: segment 3: paddr=0002f654 vaddr=00000000 size=009c4h (  2500) 
I (159) esp_image: segment 4: paddr=00030020 vaddr=400d0020 size=3cea0h (249504) map
I (259) boot: Loaded app from partition at offset 0x10000
I (259) boot: Disabling RNG early entropy source...

... it seems you are at ~ 260ms BEFORE the bootloader is even giving the control to the Rust app, so whatever you optimize, you should probably start from what the bootloader is doing for so long in the first place. Which has nothing to do with the Rust app itself.

  1. Are these logs reliable at all?

You are just looking at the UART output which is not ideal. For one, the logging via UART itself might be delaying your boot, as it takes time.

  1. Are you comparing apples to oranges?

The logs you pasted from the PlatformIO boot do not seem to contain the ESP IDF bootloader at all. Are you comparing an ESP IDF application (written in Rust) to another ESP IDF application (written in C/C++) really? With same log levels and all?

@ivmarkov
Copy link
Collaborator

Or to put it in another way, where the Rust code actually even starts is after this log line:

I (402) main_task: Calling app_main()

@markjfisher
Copy link
Author

I appreciate there are differences in what I'm comparing.

I understand that the 402ms mark is where the rust code actually runs, I'm trying to work out how to minimize the time to get to that point in a rust-template application.
Is that purely in the domain of sdkconfig.defaults? How can I configure the boot, esp_image, and cpu_start etc tasks to not take so long maybe more pertinent questions.

The logs from PIO I presented are from the main() method for the PIO app.
I do not believe there is a 400ms IDF startup time before this, as the devices we are attaching to would have failed by then.

However, I will try and find out if there is a complete system up time value I can log to see if main is actually starting later than I think, to bring the comparison more in line.

Regarding your links I did already try adding some of the tweaks we have in sdkconfig.defaults but was unable to get the base 400 value down significantly (best was "calling app_main" starts at 388 instead of 402), with these settings:

These are the main settings I pulled across:

CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_BOOTLOADER_LOG_LEVEL=0
CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON=y

CONFIG_RTC_CLK_SRC_INT_RC=y
CONFIG_RTC_CLK_CAL_CYCLES=0
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=32768

CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y

I tried turning off logging completely with:

CONFIG_LOG_DEFAULT_LEVEL_NONE=y

but it still logs up to "boot: Disabling RNG early entropy source", at 243ms, but then I can't measure when my app has started as there's no logging. I did find some information somewhere about being unable to turn off boot logging completely, so can't gain any benefits there if that is adding overhead.

Some other settings from the tweaks page I have tried are also below.

This made no difference

CONFIG_COMPILER_OPTIMIZATION_PERF=y

Neither did this:

CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y

Adding this in place of POWER_ON version didn't reduce it either:

CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y

I will continue to investigate the PIO platform too.

@markjfisher
Copy link
Author

I added a log for the esp_timer_get_time() value (device time up in microseconds) which displays it's been up 9.844ms when it logs the line "FujiNet v1.3 2024-04-26 06:08:28 (IEC) Started".

However, that appears to be a timer started just before app_main starts according to https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html, so is missing any activity prior to it being initialised.

At most however, I can see a difference of 60ms between pressing reset (the last log is timed as it shuts down) and the startup message of my application, so it is still well below 380-400ms as I'm seeing with a simple template esp-idf application.

@ivmarkov
Copy link
Collaborator

What is most important to understand is that - overall - your problem is not a "how to minimize the time to get to that point in a rust-template application" but rather "how to minimize the boot time of ANY ESP-IDF application".

So you might have more success if you read the ESP IDF documentation and look what other people have posted on the esp32.com and other ESP IDF related forums because - again - your problem is not specific to the Rust portion at all.

With that said, some specific points:

I understand that the 402ms mark is where the rust code actually runs, I'm trying to work out how to minimize the time to get to that point in a rust-template application. Is that purely in the domain of sdkconfig.defaults? How can I configure the boot, esp_image, and cpu_start etc tasks to not take so long maybe more pertinent questions.

Yes, it IS ESP IDF specific because NO Rust code executes at that point. See above. Yes, it is primarily related to sdkconfig.defaults. (Post-bootloader-boot, it might be affected also by how many ESP IDF components you are using in your Rust app, but you are not there yet.)
All of your questions are ESP IDF questions (and might be answered by ESP IDF documentation & forums) rather than something specific to the Rust wrappers.

The logs from PIO I presented are from the main() method for the PIO app. I do not believe there is a 400ms IDF startup time before this, as the devices we are attaching to would have failed by then.

What happens before the main() method? Why are you not providing the logs for the stuff which happens before the main() method? Is this even an ESP IDF application, or is this something else? Is this using the ESP IDF bootloader and if yes, where are the ESP IDF bootloader logs?

These are the questions which might give you some revelation what is happening.

Regarding your links I did already try adding some of the tweaks we have in sdkconfig.defaults but was unable to get the base 400 value down significantly (best was "calling app_main" starts at 388 instead of 402), with these settings:

These are the main settings I pulled across:

CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_BOOTLOADER_LOG_LEVEL=0
CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON=y

CONFIG_RTC_CLK_SRC_INT_RC=y
CONFIG_RTC_CLK_CAL_CYCLES=0
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=32768

CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y

I tried turning off logging completely with:

CONFIG_LOG_DEFAULT_LEVEL_NONE=y

All configuration options in sdkconfig.defaults which affect the bootloader (usually these are the ones which start with CONFIG_BOOTLOADER_) are only used when compiling the bootloader itself. So unless you flash also the newly-compiled bootloader, you won't see any change in the bootloader process, because you are still using the OLD bootloader.

The bootloader which is built as a side-effect from building the template app is available in target/xtensa-esp32-espidf/debug/bootloader.bin and you have to pass it to espflash (or whatever you use for flashing) so that it can be flashed. Otherwise, you might still be using the stock bootloader espflash is distributing embedded in itself.

CONFIG_COMPILER_OPTIMIZATION_PERF=y

Neither did this:

CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y

I wouldn't expect these to have any meaningful impact either.

Adding this in place of POWER_ON version didn't reduce it either:

CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y

A bootloader option! See above.

I will continue to investigate the PIO platform too.

BTW: there is NO "PIO platform". PIO is just a build script plus a bunch of other IDE-related utilities.
Internally, it just uses the SDK that the specific Vendor provides for its MCU line.
For esp32* MCUs, this is usually either:

  • ESP IDF
  • Or the Espressif-Arduino libs (which do live on top of ESP IDF)

So for "PIO platform" (your C++ app actually) you could start by investigating what exactly is it using? Arduino? ESP IDF directly? Something weird like NuttX or other 3rd party OS? Is it using the ESP IDF bootloader (99.9% likely, regardless of what lives on top of it). If yes, why you don't see its logs? If this is because the logs for that bootloader are disabled, how about figuring out where is the sdkconfig.defaults or other sdkconfig* which configures the "PIO platform" app itself and then understanding what opt settings they did apply?

@markjfisher
Copy link
Author

markjfisher commented Jul 19, 2024

We're using PlatformIO, on ESP-IDF:

[env]
framework = espidf

That's why I'm using the esp-idf-template and reported what I'm finding here, as I was trying to get like for like environment built, but instead of c++, using rust.

Why are you not providing the logs for the stuff which happens before the main() method?

There aren't any, I've given you the complete log I see on bootup and on reset.

I'll look deeper into the bootloader and if there are any options to disable its logging, but as far as I can tell from the esp docs, the value I'm using is minimal:

CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_BOOTLOADER_LOG_LEVEL=0

you have to pass it to espflash (or whatever you use for flashing)

I'm simply using cargo run as per my logs in the original post, and as reported in the original log, it uses espflash.
The last log I have for this is:

     Running `espflash flash --monitor target/xtensa-esp32s3-espidf/debug/fujinet-hal-rust`
[2024-07-19T10:32:31Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-07-19T10:32:31Z INFO ] Connecting...
[2024-07-19T10:32:31Z INFO ] Using flash stub
Chip type:         esp32s3 (revision v0.1)
Crystal frequency: 40 MHz
Flash size:        8MB
Features:          WiFi, BLE
MAC address:       f4:12:fa:da:dd:84
App/part. size:    499,136/1,048,576 bytes, 47.60%
[2024-07-19T10:32:33Z INFO ] Segment at address '0x0' has not changed, skipping write
[2024-07-19T10:32:33Z INFO ] Segment at address '0x8000' has not changed, skipping write
[00:00:26] [========================================]     264/264     0x10000                                                                                                                                                                                                                                       [2024-07-19T10:33:00Z INFO ] Flashing has completed!
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

ESP-ROM:esp32s3-20210327

I don't know if this means the bootloader was flashed or not, and I haven't found any docs that indicate how I can force it with cargo.

I clearly did find cargo espflash at some point, as that is in my history, and I've used it to erase-flash, but I don't see an option the write the bootloader (https://github.com/esp-rs/espflash/tree/main/espflash#usage)

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jul 19, 2024

I clearly did find cargo espflash at some point, as that is in my history, and I've used it to erase-flash, but I don't see an option the write the bootloader (https://github.com/esp-rs/espflash/tree/main/espflash#usage)

espflash flash --help

================

Don't use cargo run. Don't use cargo espflash.
You are chasing an advanced topic, yet you are still using the beginners' setup (cargo run) to build and flash. There is a way to configure cargo run to flash the custom bootloader, but I don't remember what it was right now (some sort of cargo.metadata in Cargo.toml)

Use this instead:

  1. cargo build -> this will build:
  • ESP IDF itself (this is a static lib)
  • The "Rust app" that uses the ESP IDF static lib. The result is an ELF executable here: target/xtensa-es32-espidf/debug/<whatever-your-esp-idf-template-project-is-named>
  • The bootloader.bin we discussed previously
  • A partition-table.bin at the same location you don't usually need
  1. espflash flash ...
  • This will just flash your ELF binary from Step 1, plus optionally, it can flash also the custom bootloader we discussed previously. Please look at the espflash flash --help options to figure out how to flash the custom bootloader in addition to the ELF executable
  • JFYI, the ELF executable usually needs to be converted to a ".bin" format (the bootloader is already converted, hence why it is named bootloader.bin) but espflash does this for the main app automatically during flashing, unlike - say - esptool

====

Also, since the "PIO platform" app is a stock, boring ESP IDF app after all, your best bet is to look at its own sdkconfig so as to figure out what optimization settings they (the creators of the "PIO platform" app) have applied. To the bootloader and to the main app. Because chances are, you can apply those 1:1 in your "Rust template app" and thus achieve identical boot times. I know I'm repeating myself, but I'm not sure you understood that. If you understood, sorry for the verbosity.

@ivmarkov
Copy link
Collaborator

Before I forget: apply cargo clean for a full, clean rebuild so that you are 100% sure all CONFIG_BOOTLOADER options are really applied.

@ivmarkov
Copy link
Collaborator

ivmarkov commented Jul 19, 2024

That's why I'm using the esp-idf-template and reported what I'm finding here, as I was trying to get like for like environment built, but instead of c++, using rust.

Why are you not providing the logs for the stuff which happens before the main() method?

There aren't any, I've given you the complete log I see on bootup and on reset.

Which means very likely they've disabled ALL logging in their custom built bootloader plus who knows what else. So (for the third time) - hunt for their sdkconfig*, try to apply their options to your Rust app + bootloader, and you'll likely achieve identical boot times. :)

@markjfisher
Copy link
Author

markjfisher commented Jul 19, 2024

I was struggling to flash the esptool generated bin file:

$ esptool.py --chip esp32s3 elf2image target/xtensa-esp32s3-espidf/debug/fujinet-hal-rust
esptool.py v4.7.0
Creating esp32s3 image...
Merged 2 ELF sections
Successfully created esp32s3 image.

$ espflash flash -c esp32s3 --bootloader target/xtensa-esp32s3-espidf/debug/bootloader.bin -p /dev/ttyUSB0 target/xtensa-esp32s3-espidf/debug/fujinet-hal-rust.bin 
[2024-07-19T11:53:59Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-07-19T11:53:59Z INFO ] Connecting...
[2024-07-19T11:53:59Z INFO ] Using flash stub
Chip type:         esp32s3 (revision v0.1)
Crystal frequency: 40 MHz
Flash size:        8MB
Features:          WiFi, BLE
MAC address:       f4:12:fa:da:dd:84
Bootloader:        target/xtensa-esp32s3-espidf/debug/bootloader.bin
Error: espflash::invalid_elf

  × Supplied ELF image is not valid
  ╰─▶ Did not find ELF magic number
  help: Try running `cargo clean` and rebuilding the image

I've just noticed it says it requires an ELF image, which I've directly used instead of the esptool generated bin file, and it works fine, so I can flash directly from files instead of using cargo, which is useful:

$ espflash erase-flash
[2024-07-19T12:48:43Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-07-19T12:48:43Z INFO ] Connecting...
[2024-07-19T12:48:43Z INFO ] Using flash stub
[2024-07-19T12:48:45Z INFO ] Erasing Flash...
[2024-07-19T12:49:07Z INFO ] Flash has been erased!

$ espflash flash -c esp32s3 --bootloader target/xtensa-esp32s3-espidf/debug/bootloader.bin -p /dev/ttyUSB0 target/xtensa-esp32s3-espidf/debug/fujinet-hal-rust
[2024-07-19T12:49:14Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-07-19T12:49:14Z INFO ] Connecting...
[2024-07-19T12:49:14Z INFO ] Using flash stub
Chip type:         esp32s3 (revision v0.1)
Crystal frequency: 40 MHz
Flash size:        8MB
Features:          WiFi, BLE
MAC address:       f4:12:fa:da:dd:84
Bootloader:        target/xtensa-esp32s3-espidf/debug/bootloader.bin
App/part. size:    499,136/1,048,576 bytes, 47.60%
[00:00:00] [========================================]      10/10      0x0
[00:00:00] [========================================]       1/1       0x8000
[00:00:26] [========================================]     264/264     0x10000
[2024-07-19T12:49:44Z INFO ] Flashing has completed!

However, armed with this knowledge, I tried it via cargo and that produces the same results, there's an entry in the "espflash.toml" file I added to point to the bootloader (you can see it mentioned in the logs)

$ cargo espflash erase-flash
[2024-07-19T12:37:01Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-07-19T12:37:01Z INFO ] Connecting...
[2024-07-19T12:37:01Z INFO ] Using flash stub
[2024-07-19T12:37:03Z INFO ] Erasing Flash...
[2024-07-19T12:37:26Z INFO ] Flash has been erased!

$ cargo run
    Finished dev [optimized + debuginfo] target(s) in 0.23s
     Running `espflash flash --monitor target/xtensa-esp32s3-espidf/debug/fujinet-hal-rust`
[2024-07-19T12:37:40Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-07-19T12:37:40Z INFO ] Connecting...
[2024-07-19T12:37:41Z INFO ] Using flash stub
Chip type:         esp32s3 (revision v0.1)
Crystal frequency: 40 MHz
Flash size:        8MB
Features:          WiFi, BLE
MAC address:       f4:12:fa:da:dd:84
Bootloader:        target/xtensa-esp32s3-espidf/debug/bootloader.bin
App/part. size:    499,136/1,048,576 bytes, 47.60%
[00:00:00] [========================================]      10/10      0x0
[00:00:00] [========================================]       1/1       0x8000
[00:00:26] [========================================]     264/264     0x10000
[2024-07-19T12:38:11Z INFO ] Flashing has completed!
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x28 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fce3820,len:0x1ac
load:0x403c9700,len:0x4
load:0x403c9704,len:0xb20
load:0x403cc700,len:0x27bc
entry 0x403c9868
I (34) cpu_start: Multicore app
I (38) cpu_start: Pro cpu start user code
I (38) cpu_start: cpu freq: 160000000 Hz
I (38) cpu_start: Application information:
I (41) cpu_start: Project name:     libespidf
I (46) cpu_start: App version:      1
I (50) cpu_start: Compile time:     Jul 19 2024 11:32:09
I (56) cpu_start: ELF file SHA256:  000000000...
I (62) cpu_start: ESP-IDF:          v5.2.2
I (66) cpu_start: Min chip rev:     v0.0
I (71) cpu_start: Max chip rev:     v0.99 
I (76) cpu_start: Chip rev:         v0.1
I (80) heap_init: Initializing. RAM available for dynamic allocation:
I (88) heap_init: At 3FC935C0 len 00056150 (344 KiB): RAM
I (93) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM
I (99) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (106) heap_init: At 600FE010 len 00001FC8 (7 KiB): RTCRAM
I (113) spi_flash: detected chip: generic
I (117) spi_flash: flash io: qio
W (120) spi_flash: Detected size(8192k) larger than the size in the binary image header(4096k). Using the size in the binary image header.
W (134) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (142) sleep: Configure to isolate all GPIO pins in sleep state
I (149) sleep: Enable automatic switching of GPIO sleep configuration
I (156) main_task: Started on CPU0
I (160) main_task: Calling app_main()
I (165) fujinet_hal_rust: Hello, world!

So as you can see, with just the settings I was trying earlier it's down to 160ms to get to app_main now. I'll see what else I can tweak now I know how to get the bootloader flashed.

The project I'm heavily involved with (the ones you keep referring to as "they") does include myself, I'm more heavily involved in the software side, but "we" haven't done any extensive changes to the sdkdefaults other than the original ones I posted above that I'm aware of. I'm off to look further on this now I know how to flash the bootloader.

I think the key part I was missing was the bootloader not getting flashed by the default "cargo run".

I'm not sure all of your comments about not using cargo espflash etc is quite on the ball, but your breakdown of the individual parts was extremely useful to know so I thank you for your insight in that, it's got me to a position I'm happy I could continue dev in rust on esp32 with. I'm extremely happy with that!

The target I'm using is esp32s3, but the fujinet project uses the standard esp32, which is why I'm not simply copying the entire sdkconfig.defaults file across, as there are incompatibilities, and the original is generated by platformio for the most part, and I'm trying to grow the new s3 file with values to understand how they affect performance and logging etc.

@markjfisher
Copy link
Author

markjfisher commented Jul 19, 2024

Just to add, I commented out the line in espidf.rs for the bootloader, and it went back to 400ms doing cargo run after flashing what must be a default file.

Then readding the bootloader = ... line and cargo run again flashed the new bootloader and brought the time down to 160ms again, so it appears cargo run (when told the correct bootloader path) will flash it for you:

MAC address:       f4:12:fa:da:dd:84
Bootloader:        target/xtensa-esp32s3-espidf/debug/bootloader.bin
App/part. size:    499,136/1,048,576 bytes, 47.60%
[00:00:00] [========================================]      10/10      0x0
[2024-07-19T13:06:48Z INFO ] Segment at address '0x8000' has not changed, skipping write
[2024-07-19T13:06:48Z INFO ] Segment at address '0x10000' has not changed, skipping write
[2024-07-19T13:06:48Z INFO ] Flashing has completed!

I'm also seeing that adding entries to the sdkconfig.defaults file that affects bootlader now automatically rebuilds and flashes it too.

@ivmarkov
Copy link
Collaborator

I was struggling to flash the esptool generated bin file:

(snip)

I've just noticed it says it requires an ELF image, which I've directly used instead of the esptool generated bin file, and it works fine, so I can flash directly from files instead of using cargo, which is useful:

  • espflash can flash an ELF file directly, as it has a logic inside, which converts an ELF file to a .BIN file on-the-fly during flashing
  • esptool can only flash a BIN file, so you need to convert the ELF file which is the result from building a Rust or a C++ or a C application to BIN explicitly
$ espflash erase-flash

I've never used erase-flash and I'm not sure why you need this. Flash is anyway erased when you re-flash.

So as you can see, with just the settings I was trying earlier it's down to 160ms to get to app_main now. I'll see what else I can tweak now I know how to get the bootloader flashed.

I think we are aligned here? I guess I am - like a broken record - repeating that there is nothing Rust-specific in your task and it is all about ESP IDF bootloader optimization and possibly pre-app-main optimization. For now at least. :)

The project I'm heavily involved with (the ones you keep referring to as "they") does include myself, I'm more heavily involved in the software side, but "we" haven't done any extensive changes to the sdkdefaults other than the original ones I posted above that I'm aware of. I'm off to look further on this now I know how to flash the bootloader.

Well there is no way for me to know that so I am just trying to be super clear what I mean even at the cost of sounding a bit offensive - sorry for that.

with regards to the 160ms that you are still aiming to lower to 40ms - I'm pretty confident it is another option in the bootloader OR in the app startup (pre-app-main). So it is just a matter of hunting the relevant options from the existing C++ sdkconfigs and applying those to the esp template app.

I think the key part I was missing was the bootloader not getting flashed by the default "cargo run".

I'm not sure all of your comments about not using cargo espflash etc is quite on the ball, but your breakdown of the individual parts was extremely useful to know so I thank you for your insight in that, it's got me to a position I'm happy I could continue dev in rust on esp32 with. I'm extremely happy with that!

Happy to see your progress and excitement!
JFYI:

  • There is a separate set of crates - the so called esp-hal ones. You might want to look into those as well if you haven't yet, though migration from an ESP IDF C project to ESP IDF Rust project should be much easier
  • On the topic of migration, the CMake build option might be an easier migration path for you guys as that way you might be introducing Rust to your otherwise C/C++ codebase gradually, so you might want to look at the CMake generator as well. Of course, if your custom codebase is rather on the small side, it is not worth it, and it is probably more straightformward to just migrate it all to Rust. But in that case maybe consider the no_std esp-hal as well

The target I'm using is esp32s3, but the fujinet project uses the standard esp32, which is why I'm not simply copying the entire sdkconfig.defaults file across, as there are incompatibilities, and the original is generated by platformio for the most part, and I'm trying to grow the new s3 file with values to understand how they affect performance and logging etc.

I don't think "platformio" is in the "sdkconfig" generation business at all. Either you maintain - manually and carefully - an sdkconfig.defaults file, or you use the sdkconfig monstrocity generated by idf.py menuconfig (or whatever the equivalent was in pio's build wrappers). If the latter, I agree it is difficult to spot what is changed in sdkconfig compared to the defaults, but I think this is still possible... and might be useful for you in hunting these extra 120ms which are between you and the 40ms target.

There is a small chance that you might be hitting something esp32s3 specific w.r.t. startup time though I doubt that.
You've probably seen this already: https://docs.espressif.com/projects/esp-techpedia/en/latest/esp-friends/advanced-development/performance/reduce-boot-time.html ?

@markjfisher
Copy link
Author

markjfisher commented Jul 19, 2024

Since my last post, I've been gradually introducing the sdkconfig defaults values from our project to see what impact each section has and if there's a setting that turns off further startup logging. Some values (particularly around PSRAM) appear to make the s3 fail to boot, which is why I was keen to add them block by block, but it's taking a while.

The sdkconfig.defaults file has a big message at the top saying not to edit as it's generated, and if I remove my board specific version from my project and run the pio build, it does get re-generated for me, so I'm pretty sure it's doing something in that area!

I'll close this ticket down soon, just thought I'd continue the dialog of my findings until I've completed the config changes.

like a broken record - repeating that there is nothing Rust-specific in your task and it is all about ESP IDF bootloader optimization and possibly pre-app-main optimization

Understood completely, and I've also been saying that by asking how I can optimise the stuff before the rust code runs.

If nothing else, hopefully this will help anyone else trying to find out how to slim down the template app startup, or provide an entertaining read. We live in hope.

@markjfisher
Copy link
Author

markjfisher commented Jul 19, 2024

It appears our project has turned off all logging below Error, but has a special logger for debug that it uses.

I've done similar, set default logging to error, and simply changed log output in my app to print errors to test how fast it gets there.

It's down to 40ms, which is superb, proving it can be done.

$ cargo run
   Compiling esp-idf-sys v0.35.0
   Compiling esp-idf-hal v0.44.0
   Compiling esp-idf-svc v0.49.0
   Compiling fujinet-hal-rust v0.1.0 (/home/fenrock/dev/rust/fujinet-hal-rust)
    Finished dev [optimized + debuginfo] target(s) in 27.76s
     Running `espflash flash --monitor target/xtensa-esp32s3-espidf/debug/fujinet-hal-rust`
[2024-07-19T16:02:20Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-07-19T16:02:20Z INFO ] Connecting...
[2024-07-19T16:02:20Z INFO ] Using flash stub
Chip type:         esp32s3 (revision v0.1)
Crystal frequency: 40 MHz
Flash size:        8MB
Features:          WiFi, BLE
MAC address:       f4:12:fa:da:dd:84
Bootloader:        target/xtensa-esp32s3-espidf/release/bootloader.bin
App/part. size:    504,880/1,048,576 bytes, 48.15%
[2024-07-19T16:02:22Z INFO ] Segment at address '0x0' has not changed, skipping write
[2024-07-19T16:02:22Z INFO ] Segment at address '0x8000' has not changed, skipping write
[00:00:26] [========================================]     266/266 0x10000
[2024-07-19T16:02:50Z INFO ] Flashing has completed!
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x28 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fce3820,len:0x55c
load:0x403c9700,len:0x4
load:0x403c9704,len:0xb68
load:0x403cc700,len:0x2858
entry 0x403c9880
E (40) fujinet_hal_rust: Started main
E (53) fujinet_hal_rust: After 2ms!
E (60) fujinet_hal_rust: After 7ms!
E (75) fujinet_hal_rust: After 15ms!
E (97) fujinet_hal_rust: After 22ms!

Thanks for your help and information, I didn't react to all your comments, but did read them, and will slowly digest them.
This is really a POC at the moment, but good to see it's viable

@github-project-automation github-project-automation bot moved this from Todo to Done in esp-rs Jul 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

2 participants