micropython/micropython-esp32

Bluetooth support

MrSurly opened this issue · 33 comments

I've abandoned my PR for Bluetooth support because the IDF has changed so much, large portions of the code no longer work or are irrelevant. Modifying the old code would be more work than just starting anew, copying snippets from the old codebase as necessary.

Having said that, I've created a dev-bluetooth-2 branch, with the goal if just getting network.Bluetooth() working. That is, simply initializing the BT subsystem, and nothing else.

While it does compile, it crashes (gibberish to console, WDT a few seconds later) when calling esp_bt_controller_init().

I'm hoping for some guidance from others that know the IDF & MicroPython better than I do. If @projectgus, @igrr, @dpgeorge or anyone else has any hints, it'd be much appreciated. The IDF hash is 2c95a77cf93781f296883d5dbafcdc18e4389656, which currently is the hash used by MicroPython ESP32.

Thanks.

@dpgeorge Oh, also, this would be a good time to work on a HAL layer for BT in general, as was discussed.

Okay, I've looked at this a bit further. There doesn't appear that there's enough heap to support Bluetooth on the ESP-WROOM-32 alongside MicroPython. Perhaps one of the other ESP modules that have psRAM support will work.

What makes BLE so heap-intensive compared to, say, WLAN?

Well, right off the bat, it takes 64K from the heap for the BT stack. This is in the linker script for the IDF. That doesn't include all the bss items that eat up the heap.

I added this line:

ESP_LOGI(TAG, "Free RAM for user task: %d", xPortGetFreeHeapSize());

at line 373 of esp32/cpu_start.c in the IDF, before the call to vTaskStartScheduler();

examples/system/deepsleep: 178388
examples/bluetooth/gatt_client: 73420
Current MP: 7232 (with 96K MP heap) = 105536
Current MP: 80444 (with 6K MP heap)

Those last two lines don't make any sense to me. If I use a 96K MP heap, then there's a report 7232b free. If I use a 6K MP heap (-90K), then the free ram only jumps to 80444. Shouldn't it jump to 99392 (96K + 7232)?

  178388            Baseline
- 104968            Bluetooth
-  72852            MicroPython (with MP heap size of 0)
=    568            Free

That does seem a bit arbitrary. Is that this bit in components/soc/esp32/soc_memory_layout.c you're referring to?

#if CONFIG_BT_ENABLED
    { 0x3ffb0000, 0x3ffc0000 }, //Reserve BT hardware shared memory & BT data region

I wonder if the BT hardware controller can be asked to use just a smidge less memory ... there's code in components/bt/bt.c which sets up a mapping of available memory regions, perhaps this can be tweaked a little. Also perhaps if you're only interested in BLE then CLASSIC_BT can be disabled or whatever.

I'll follow up with Espressif ...

That does seem a bit arbitrary. Is that this bit in components/soc/esp32/soc_memory_layout.c you're referring to?

That may very well be part of it, but I was thinking of CONFIG_BT_RESERVE_DRAM

From sdkconfig.h

#define CONFIG_BT_RESERVE_DRAM 0x10000

This is used in components/esp32/ld/esp32.ld

Well, right off the bat, it takes 64K from the heap for the BT stack

I'll follow up with Espressif ...

Hi folks,

As you've mentioned, the ROM-resident part of the Bluetooth hardware controller reserves a region of memory for static memory - buffers, etc. The region is compiled into the ROM code which is why it's blocked out as an absolute range in the linker script.

In IDF v2.1 you could use menuconfig to reduce the size of this region if you were not using BT Classic (ie BLE-only).

In IDF v3.0, in an effort to make this more flexible, you can release the Bluetooth controller memory at runtime. See esp_bt_controller_mem_release():
https://esp-idf.readthedocs.io/en/latest/api-reference/bluetooth/controller_vhci.html#_CPPv229esp_bt_controller_mem_release13esp_bt_mode_t

It's possible to either release the BT-Classic-only memory at runtime (approx 30KB of the 64KB), or to release the entire 64KB. It's not possible to release memory which is currently used by the Bluetooth controller. Releasing BT controller memory is permanent, the only way to claim it back (ie to re-enable those BT modes) is to restart.

Regarding memory for the MicroPython heap, there are two other (potential) issues which spring to mind here:

  • MicroPython heap is required to be contiguous bytes, and ESP32 heap is non-contiguous (although most of it is one large contiguous region).
  • MicroPython heap (mp_task_heap variable) is static allocated, and there is currently a limit to the amount of data which can be static allocated before IDF fails to boot. There is a PR currently open at espressif/esp-idf#1238 which should improve this situation. I don't know if MicroPython is currently running into this problem or not, as it sounds like you're running out of total heap at runtime regardless (at least when BT is on).

Those last two lines don't make any sense to me. If I use a 96K MP heap, then there's a report 7232b free. If I use a 6K MP heap (-90K), then the free ram only jumps to 80444. Shouldn't it jump to 99392 (96K + 7232)?

That seems odd to me. You may get some more clues by calling heap_caps_print_heap_info(MALLOC_CAP_8BIT):
https://esp-idf.readthedocs.io/en/latest/api-reference/system/mem_alloc.html#_CPPv225heap_caps_print_heap_info8uint32_t

Thanks @projectgus for the info. It looks like we can make progress here in a few ways: disable classic BT so less RAM is needed overall, and have some way to completely disable BT from within Python so that a user can reclaim memory if they don't need BLE. Also, it may be better to allocate the MP heap dynamically (at runtime) to get around any static allocation issues.

@MrSurly I tried the newest commit on the branch and keep crashing.
Does the current commit work for you?

@MrSurly I tried the newest commit on the branch and keep crashing. Does the current commit work for you?

No, that's the point of this post.

G'day @bmeisels

Sadly the BT support in MicroPython/ESP32 is still very much a work in progress and we've hit a number of problems getting it implemented ... see the comments in this issue for details. I'm hoping we'll get there eventually but it'll require more work to get something usable ...

@nickzoic @MrSurly
I understand that this feature is far from production. I must have skipped over the part in the original post mentioning it doesn't run.

The previous attempt (dev-bluetooth) worked when I tested it. What has changed in dev-bluetooth-2 which is causing increased use of memory? Is this caused by the updated idf or by changes to the implementation of the Bluetooth micropython module?

I would like to help with debugging and trying to get this working.

MP has been updated to a relatively new version of the IDF -- as I stated above, perhaps too many static allocations in the .BSS segment? But that's just a guess. dev-bluetooth-2 doesn't even really have any code to speak of; just turning on BT in sdkconfig is enough to eat up all the RAM.

I still want to make BLE work in MP (would make my life much easier -- I have projects I've re-written from Python to C++), but my bandwidth is severely limited right now.

I've tried this branch but it doesn't seem to work at all. The error:

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:0x3fff0018,len:4
load:0x3fff001c,len:4324
load:0x40078000,len:0
load:0x40078000,len:10992
entry 0x4007a6c4
I (426) cpu_start: Pro cpu up.
I (426) cpu_start: Single core mode
I (426) heap_init: Initializing. RAM available for dynamic allocation:
I (429) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (435) heap_init: At 3FFDC360 len 00003CA0 (15 KiB): DRAM
I (442) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (448) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (454) heap_init: At 40092CE8 len 0000D318 (52 KiB): IRAM
I (461) cpu_start: Pro cpu start user code
I (31) cpu_start: Starting scheduler on PRO CPU.
abort() was called at PC 0x400824c6 on core 0

Backtrace: 0x4008a5f3:0x3ffe3bc0 0x4008a61f:0x3ffe3be0 0x400824c6:0x3ffe3c00 0x400825a7:0x3ffe3c30 0x4007a63f:0x3ffe3c70 0x4007a7d2:0x3ffe3e70 0x40007c31:0x3ffe3eb0 0x4000073d:0x3ffe3f20

Rebooting...
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
Guru Meditation Error of type IllegalInstruction occurred on core  0. Exception was unhandled.
[...many more of these lines until the WDT kicks in...]

It may be caused by a faulty toolchain/SDK setup, see #237.

Note that this error occurs even before I get to a REPL.


With self-built toolchain (not the one from Espressif) the error is a bit different, without the Guru Meditation Error. Apparently it calls abort() from within start_cpu0_default due to lack of memory:

rst:0x1 (POWERON_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:0x3fff0018,len:4
load:0x3fff001c,len:4348
load:0x40078000,len:0
load:0x40078000,len:10992
entry 0x4007a6c4
I (426) cpu_start: Pro cpu up.
I (426) cpu_start: Single core mode
I (426) heap_init: Initializing. RAM available for dynamic allocation:
I (429) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (436) heap_init: At 3FFDC370 len 00003C90 (15 KiB): DRAM
I (442) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (448) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (454) heap_init: At 40092CE8 len 0000D318 (52 KiB): IRAM
I (461) cpu_start: Pro cpu start user code
I (31) cpu_start: Starting scheduler on PRO CPU.
abort() was called at PC 0x400824c6 on core 0

Backtrace: 0x4008a5f3:0x3ffe3bc0 0x4008a61f:0x3ffe3be0 0x400824c6:0x3ffe3c00 0x400825a7:0x3ffe3c30 0x4007a63f:0x3ffe3c70 0x4007a7d2:0x3ffe3e70 0x40007c31:0x3ffe3eb0 0x4000073d:0x3ffe3f20

Rebooting...
 0 0L��?���@3

I'm pretty sure it's this line in ESP-IDF calling abort().

I've tried this branch but it doesn't seem to work at all.

Correct. Have you read through the comments posted here?

Correct. Have you read through the comments posted here?

Yes. My interpretation was that it stopped working with any call to the bluetooth subsystem (from Python), but in my case it doesn't even get to a REPL - it crashes on boot.

I managed to save 33.8kB of RAM, by disabling LAN, see https://github.com/MrSurly/micropython-esp32/pull/17. This way I was able to enter the REPL and enable the BT stack without problems (I think):

MicroPython v1.9.2-452-g2b3b1a7a9-dirty on 2017-12-10; ESP32 module with ESP32
Type "help()" for more information.
>>> import network
>>> bt = network.Bluetooth()
enter network_bt_make_new
before network_bt_init
past queue initializations
before esp_bt_controller_init
I (404027) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
after esp_bt_controller_init
before esp_bt_controller_enable
W (405197) phy_init: failed to load RF calibration data (0x1102), falling back to full calibration
I (405357) phy: phy_version: 362.0, 61e8d92, Sep  8 2017, 18:48:11, 0, 2
before esp_bluedroid_init
E (405367) BT: config_parse returned with err code: 2

before esp_bluedroid_enable
before esp_ble_gap_register_callback
after network_bt_init
>>> 

I managed to enable advertisement, showing that it really is working :D
The device is visible from my phone (using nRF Connect). I added advertisement support with this commit: aykevl@2f8dd92

It is also possible to enable GATTS and GATTC. They are enabled, although GATTC logs an error during initialization (E (96244) BT: btc_gattc_call_handler()). Advertisement still works, though.

You can find such chunks of memory by using this method - but sadly it doesn't differentiate between .data and .rodata (the latter doesn't take up RAM).

@aykevl It's a start. If it's possible to move forward w/ BLE, then the real issue to hammer out is the API

If it's possible to enable Ethernet at runtime only if it's used, I'm definitely all for that ... I thought we already were doing that?

We're calling "init" at runtime. But just compiling it in reserves .BSS.

I plan to look into enabling Ethernet at runtime, unless someone else picks it up.
Note that it not only helps Bluetooth, but would probably make it possible to enlarge the MP heap to 128kB (from 96kB) - a significant improvement.

EDIT: if anyone wants to take a look, the buffers are defined here: https://github.com/espressif/esp-idf/blob/master/components/ethernet/emac_main.c#L63

@MrSurly , do you solve the problem, I also port the latest esp-idf one month ago, and when i call
esp_bt_controller_init, it will cause crash. I have no idea for it.

@YanMinge
Development has moved here: https://github.com/MrSurly/micropython/tree/dev-bluetooth-esp32

I have not had much time to work on it.

Is there any update on the BLE progress in ESP32 MicroPython?

No, not from my end. MP development (for me) often involves having to solve a specific need. If/when I have another project that absolutely has to have BLE, I'll revisit this.

Over at the nrf port (https://github.com/tralamazza/micropython) we're thinking about working on it soonish (CC @glennrub).

The idea is to define a common API for both ports so most of the implementation can be shared between ports. This is a bit more work initially but should make it possible to use the same NUS console (REPL over BLE) on both ports, for example.

Have you tried the new boards with 4 Mb of PSRAM or more?

Refs.:

I don't think available RAM is a blocking issue, we just have to be a little bit more careful in how we allocate it. For example, the current configuration (last time I looked) was that it allocates about 90kB in .bss, which may give problems. But there is a large area of heap which is unused, where the MicroPython heap should probably be put instead (solving most of these RAM issues). It would even increase the available RAM to ~110kB.

@aykevl, thanks for that information. That's great because once the issue is resolved, we can use the standard ESP32 boards with no PSRAM.

Hey, been looking forward to use BLE with esp32 and micropython and would love to help in any way if possible. Are there any updates on the progress?

The latest info is here: micropython/micropython#3809 (comment)

This fork of MicroPython has long since been merged upstream.

BLE support for esp32 is being implemented upstream via the following PR: micropython/micropython#5051