From a47ea032666f27564c40b4522d09ef9ef7790554 Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Fri, 1 May 2026 14:53:58 +0530 Subject: [PATCH] feat(nimble): Support NimBLE log compression and decompression over SPI --- components/bt/CMakeLists.txt | 4 +- components/bt/common/ble_log/Kconfig.in | 3 +- .../ble_log/deprecated/ble_log_spi_out.c | 2 +- .../extension/log_compression/CMakeLists.txt | 16 ++- .../extension/log_compression/Kconfig.in | 6 +- .../log_compression/ble_log_compression.c | 120 +++++++++++----- .../src/prph/ble_log_prph_spi_master_dma.c | 19 ++- components/bt/host/nimble/nimble | 2 +- .../bt/test_apps/basic_unit_test/README.md | 2 +- .../nimble/ble_spi_slave/CMakeLists.txt | 9 ++ .../bluetooth/nimble/ble_spi_slave/README.md | 111 +++++++++++++++ .../nimble/ble_spi_slave/main/CMakeLists.txt | 3 + .../nimble/ble_spi_slave/main/ble_spi_slave.h | 12 ++ .../nimble/ble_spi_slave/main/main.c | 131 ++++++++++++++++++ .../nimble/ble_spi_slave/sdkconfig.defaults | 7 + 15 files changed, 398 insertions(+), 49 deletions(-) create mode 100644 examples/bluetooth/nimble/ble_spi_slave/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_spi_slave/README.md create mode 100644 examples/bluetooth/nimble/ble_spi_slave/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_spi_slave/main/ble_spi_slave.h create mode 100644 examples/bluetooth/nimble/ble_spi_slave/main/main.c create mode 100644 examples/bluetooth/nimble/ble_spi_slave/sdkconfig.defaults diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 6cec63b4682..bf394b699c6 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -981,8 +981,6 @@ set(bt_priv_requires esp_driver_uart vfs esp_ringbuf - esp_driver_spi - esp_driver_gpio esp_gdbstub esp_security ) @@ -1017,7 +1015,7 @@ endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" PRIV_INCLUDE_DIRS "${priv_include_dirs}" - REQUIRES esp_timer esp_wifi + REQUIRES esp_timer esp_wifi esp_driver_spi esp_driver_gpio PRIV_REQUIRES "${bt_priv_requires}" LDFRAGMENTS "${ldscripts}") diff --git a/components/bt/common/ble_log/Kconfig.in b/components/bt/common/ble_log/Kconfig.in index 7108fd82a0e..50921c52f44 100644 --- a/components/bt/common/ble_log/Kconfig.in +++ b/components/bt/common/ble_log/Kconfig.in @@ -15,7 +15,8 @@ if BLE_LOG_ENABLED config BLE_LOG_LBM_TRANS_BUF_SIZE int "Total buffer memory per common LBM (bytes)" - default 2048 + default 512 if BT_BLUEDROID_ENABLED + default 1024 if BT_NIMBLE_ENABLED help Total buffer memory allocated for each common pool log buffer manager (LBM). This memory is divided equally among internal diff --git a/components/bt/common/ble_log/deprecated/ble_log_spi_out.c b/components/bt/common/ble_log/deprecated/ble_log_spi_out.c index 9c54242f6be..75e85bf1e22 100644 --- a/components/bt/common/ble_log/deprecated/ble_log_spi_out.c +++ b/components/bt/common/ble_log/deprecated/ble_log_spi_out.c @@ -677,6 +677,7 @@ static void spi_out_log_cb_dump(spi_out_log_cb_t *log_cb) } } +#if SPI_OUT_HOST_ENABLED || SPI_OUT_MESH_ENABLED || SPI_OUT_HCI_ENABLED || SPI_OUT_LE_AUDIO_ENABLED static void spi_out_update_task_mapping(int idx, void *ptr) { // It is a must to clear task handle after task deletion @@ -684,7 +685,6 @@ static void spi_out_update_task_mapping(int idx, void *ptr) entry->task_handle = NULL; } -#if SPI_OUT_HOST_ENABLED || SPI_OUT_MESH_ENABLED || SPI_OUT_HCI_ENABLED || SPI_OUT_LE_AUDIO_ENABLED static bool spi_out_get_task_mapping(task_map_t *map, size_t num, spi_out_log_cb_t **log_cb, uint8_t **str_buf) { diff --git a/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt b/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt index b670eebe557..a0c001998d2 100644 --- a/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt +++ b/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt @@ -9,7 +9,7 @@ set(BLE_MESH_LOG_INDEX_HEADER "\"\"") set(BLE_MESH_TAGS "") set(BLE_MESH_TAGS_PRESERVE "") -# default config value for host module +# default config value for host module (Bluedroid or NimBLE) set(HOST_CODE_PATH "") set(HOST_LOG_INDEX_HEADER "\"\"") set(BLE_HOST_TAGS "") @@ -41,6 +41,20 @@ if(CONFIG_BLE_HOST_COMPRESSED_LOG_ENABLE AND CONFIG_BT_BLUEDROID_ENABLED) "${CMAKE_CURRENT_LIST_DIR}/scripts/module_scripts/bluedroid/make_bluedroid_log_macro.py") include(${CMAKE_CURRENT_LIST_DIR}/cmake/ble_host_bluedroid_tags.cmake) + if(NOT EXISTS "${CMAKE_BINARY_DIR}/ble_log/include/${HOST_LOG_INDEX_HEADER}") + file(WRITE "${CMAKE_BINARY_DIR}/ble_log/include/${HOST_LOG_INDEX_HEADER}" "") + endif() + list(APPEND LOG_COMPRESSED_MODULE_CODE_PATH ${HOST_CODE_PATH}) +elseif(CONFIG_BLE_HOST_COMPRESSED_LOG_ENABLE AND CONFIG_BT_NIMBLE_ENABLED) + list(APPEND LOG_COMPRESSED_MODULE "BLE_HOST") + + set(HOST_CODE_PATH "host/nimble/nimble/nimble/host") + set(HOST_LOG_INDEX_HEADER "nimble_log_index.h") + set(BLE_HOST_LOG_SCRIPT_PATH + "${CMAKE_CURRENT_LIST_DIR}/scripts/module_scripts/nimble/make_nimble_log_macro.py") + set(BLE_HOST_TAGS "MODLOG_DFLT, BLE_HS_LOG") + set(BLE_HOST_TAGS_PRESERVE "") + if(NOT EXISTS "${CMAKE_BINARY_DIR}/ble_log/include/${HOST_LOG_INDEX_HEADER}") file(WRITE "${CMAKE_BINARY_DIR}/ble_log/include/${HOST_LOG_INDEX_HEADER}" "") endif() diff --git a/components/bt/common/ble_log/extension/log_compression/Kconfig.in b/components/bt/common/ble_log/extension/log_compression/Kconfig.in index 100a1dd8bd2..c95f7a23390 100644 --- a/components/bt/common/ble_log/extension/log_compression/Kconfig.in +++ b/components/bt/common/ble_log/extension/log_compression/Kconfig.in @@ -160,9 +160,9 @@ if BLE_COMPRESSED_LOG_ENABLE endif menuconfig BLE_HOST_COMPRESSED_LOG_ENABLE - bool "Enable BLE Host log compression(Preview, only Bluedroid Host for now)" + bool "Enable BLE Host log compression(Preview)" depends on BLE_COMPRESSED_LOG_ENABLE - depends on BT_BLUEDROID_ENABLED + depends on BT_BLUEDROID_ENABLED || BT_NIMBLE_ENABLED default n help Apply compression to host logs. Requires @@ -182,7 +182,7 @@ if BLE_COMPRESSED_LOG_ENABLE help Maximum output length for a single log - if BLE_HOST_COMPRESSED_LOG_ENABLE + if BLE_HOST_COMPRESSED_LOG_ENABLE && BT_BLUEDROID_ENABLED menu "Select the BTM layer log tag to be compressed" config BLE_BLUEDROID_BTM_ERROR_LOG_COMPRESSION bool "Compress error log of Bluedroid host" diff --git a/components/bt/common/ble_log/extension/log_compression/ble_log_compression.c b/components/bt/common/ble_log/extension/log_compression/ble_log_compression.c index 904bd5e284c..7938dbe143a 100644 --- a/components/bt/common/ble_log/extension/log_compression/ble_log_compression.c +++ b/components/bt/common/ble_log/extension/log_compression/ble_log_compression.c @@ -16,6 +16,14 @@ #if CONFIG_BLE_COMPRESSED_LOG_ENABLE +#define BLE_CP_DROP_LOG_PERIOD 256U + +#define BLE_CP_TRY_PUSH(expr) do { \ + if ((expr) != 0) { \ + return -1; \ + } \ + } while (0) + #define BUF_NAME(name, idx) name##_buffer##idx #define BUF_MGMT_NAME(name) name##_log_buffer_mgmt @@ -42,12 +50,18 @@ INIT_BUFFER_MGMT(mesh, LOG_CP_MAX_LOG_BUFFER_USED_SIMU); char * mesh_last_task_handle = NULL; #endif -#if CONFIG_BLE_HOST_COMPRESSED_LOG_ENABLE +#if CONFIG_BLE_HOST_COMPRESSED_LOG_ENABLE && CONFIG_BT_BLUEDROID_ENABLED DECLARE_BUFFERS(host, CONFIG_BLE_HOST_COMPRESSED_LOG_BUFFER_LEN, LOG_CP_MAX_LOG_BUFFER_USED_SIMU); INIT_BUFFER_MGMT(host, LOG_CP_MAX_LOG_BUFFER_USED_SIMU); char * host_last_task_handle = NULL; #endif +#if CONFIG_BLE_HOST_COMPRESSED_LOG_ENABLE && CONFIG_BT_NIMBLE_ENABLED +DECLARE_BUFFERS(nimble, CONFIG_BLE_HOST_COMPRESSED_LOG_BUFFER_LEN, LOG_CP_MAX_LOG_BUFFER_USED_SIMU); +INIT_BUFFER_MGMT(nimble, LOG_CP_MAX_LOG_BUFFER_USED_SIMU); +char * nimble_last_task_handle = NULL; +#endif + /* The maximum number of supported parameters is 64 */ #define LOG_HEADER(log_type, info) ((log_type << 6) | (info & 0x3f)) @@ -66,10 +80,15 @@ int ble_compressed_log_cb_get(uint8_t source, ble_cp_log_buffer_mgmt_t **mgmt) last_handle = &mesh_last_task_handle; break; #endif -#if CONFIG_BLE_HOST_COMPRESSED_LOG_ENABLE +#if CONFIG_BLE_HOST_COMPRESSED_LOG_ENABLE && (CONFIG_BT_BLUEDROID_ENABLED || CONFIG_BT_NIMBLE_ENABLED) case BLE_COMPRESSED_LOG_OUT_SOURCE_HOST: +#if CONFIG_BT_BLUEDROID_ENABLED buffer_mgmt = BUF_MGMT_NAME(host); last_handle = &host_last_task_handle; +#elif CONFIG_BT_NIMBLE_ENABLED + buffer_mgmt = BUF_MGMT_NAME(nimble); + last_handle = &nimble_last_task_handle; +#endif break; #endif default: @@ -80,10 +99,18 @@ int ble_compressed_log_cb_get(uint8_t source, ble_cp_log_buffer_mgmt_t **mgmt) for (int i = 0; i < LOG_CP_MAX_LOG_BUFFER_USED_SIMU; i++) { if (ble_log_cas_acquire(&(buffer_mgmt[i].busy))) { *mgmt = &buffer_mgmt[i]; - ble_log_cp_push_u8(*mgmt, source); + if (ble_log_cp_push_u8(*mgmt, source) != 0) { + (*mgmt)->idx = 0; + ble_log_cas_release(&((*mgmt)->busy)); + return -1; + } if (*last_handle == NULL || *last_handle != cur_handle) { - ble_log_cp_push_u8(*mgmt, LOG_HEADER(LOG_TYPE_INFO, LOG_TYPE_INFO_TASK_SWITCH)); + if (ble_log_cp_push_u8(*mgmt, LOG_HEADER(LOG_TYPE_INFO, LOG_TYPE_INFO_TASK_SWITCH)) != 0) { + (*mgmt)->idx = 0; + ble_log_cas_release(&((*mgmt)->busy)); + return -1; + } *last_handle = cur_handle; } return 0; @@ -108,8 +135,8 @@ int ble_log_compressed_hex_print_internal(ble_cp_log_buffer_mgmt_t *mgmt, uint32 { uint8_t arg_type = 0; - ble_log_cp_push_u8(mgmt, LOG_HEADER(LOG_TYPE_HEX_ARGS, args_cnt)); - ble_log_cp_push_u16(mgmt, log_index); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, LOG_HEADER(LOG_TYPE_HEX_ARGS, args_cnt))); + BLE_CP_TRY_PUSH(ble_log_cp_push_u16(mgmt, log_index)); uint8_t size_info_idx = mgmt->idx; uint8_t *cur = &(mgmt->buffer)[mgmt->idx]; uint8_t size_info = 0; @@ -117,20 +144,20 @@ int ble_log_compressed_hex_print_internal(ble_cp_log_buffer_mgmt_t *mgmt, uint32 for (size_t i = 0; i < args_cnt; i++) { if (i % 2) { arg_type = va_arg(args, size_t); - ble_log_cp_push_u8(mgmt, size_info|arg_type); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, size_info|arg_type)); size_info = 0; cur++; } else { arg_type = va_arg(args, size_t); if (i == args_cnt - 1) { - ble_log_cp_push_u8(mgmt, arg_type); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, arg_type << 4)); } else { size_info = arg_type << 4; } } if (arg_type >= ARG_SIZE_TYPE_MAX) { printf("Found invalid arg type %08lx type %d", log_index, arg_type); - return 0; + return -1; } } @@ -148,27 +175,31 @@ int ble_log_compressed_hex_print_internal(ble_cp_log_buffer_mgmt_t *mgmt, uint32 uint32_t u32v = va_arg(args, size_t); if (likely(u32v)) { if (u32v <= 0xff) { - ble_log_cp_push_u8(mgmt, 3); - ble_log_cp_push_u8(mgmt, u32v); - ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_LZU32, !(i%2)); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, 3)); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, u32v)); + BLE_CP_TRY_PUSH( + ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_LZU32, !(i%2)) + ); break; } else if (u32v <= 0xffff) { - ble_log_cp_push_u8(mgmt, 2); - ble_log_cp_push_u16(mgmt, u32v); - ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_LZU32, !(i%2)); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, 2)); + BLE_CP_TRY_PUSH(ble_log_cp_push_u16(mgmt, u32v)); + BLE_CP_TRY_PUSH( + ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_LZU32, !(i%2)) + ); break; } else { - ble_log_cp_push_u32(mgmt, u32v); + BLE_CP_TRY_PUSH(ble_log_cp_push_u32(mgmt, u32v)); } } else { - ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_AZU32, !(i%2)); + BLE_CP_TRY_PUSH(ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_AZU32, !(i%2))); } break; case ARG_SIZE_TYPE_U64: uint64_t u64v = va_arg(args, uint64_t); if (likely(u64v)) { if (unlikely(u64v >> 48)) { - ble_log_cp_push_u64(mgmt, u64v); + BLE_CP_TRY_PUSH(ble_log_cp_push_u64(mgmt, u64v)); } else { uint32_t tmpv = 0; uint8_t lz = 0; @@ -179,35 +210,37 @@ int ble_log_compressed_hex_print_internal(ble_cp_log_buffer_mgmt_t *mgmt, uint32 tmpv = u64v >> 32; } lz += __builtin_clz(tmpv) / 8; - ble_log_cp_push_u8(mgmt, lz); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, lz)); switch (8-lz) { case 5: - ble_log_cp_push_u32(mgmt, (uint32_t)u64v); + BLE_CP_TRY_PUSH(ble_log_cp_push_u32(mgmt, (uint32_t)u64v)); [[fallthrough]]; case 1: - ble_log_cp_push_u8(mgmt, (uint8_t)tmpv); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, (uint8_t)tmpv)); break; case 6: - ble_log_cp_push_u32(mgmt, (uint32_t)u64v); + BLE_CP_TRY_PUSH(ble_log_cp_push_u32(mgmt, (uint32_t)u64v)); [[fallthrough]]; case 2: - ble_log_cp_push_u16(mgmt, (uint16_t)tmpv); + BLE_CP_TRY_PUSH(ble_log_cp_push_u16(mgmt, (uint16_t)tmpv)); break; case 7: - ble_log_cp_push_u32(mgmt, (uint32_t)u64v); + BLE_CP_TRY_PUSH(ble_log_cp_push_u32(mgmt, (uint32_t)u64v)); [[fallthrough]]; case 3: - ble_log_cp_push_u8(mgmt, (uint8_t)tmpv); - ble_log_cp_push_u16(mgmt, (uint16_t)(tmpv >> 8)); + BLE_CP_TRY_PUSH(ble_log_cp_push_u8(mgmt, (uint8_t)tmpv)); + BLE_CP_TRY_PUSH(ble_log_cp_push_u16(mgmt, (uint16_t)(tmpv >> 8))); break; default: assert(0); break; } - ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_LZU64, !(i%2)); + BLE_CP_TRY_PUSH( + ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_LZU64, !(i%2)) + ); } } else { - ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_AZU64, !(i%2)); + BLE_CP_TRY_PUSH(ble_log_cp_update_half_byte(mgmt, size_info_idx + i/2, ARG_SIZE_TYPE_AZU64, !(i%2))); } break; case ARG_SIZE_TYPE_STR: @@ -216,8 +249,7 @@ int ble_log_compressed_hex_print_internal(ble_cp_log_buffer_mgmt_t *mgmt, uint32 break; default: printf("Invalid size %d\n", arg_type); - assert(0); - break; + return -1; } } return 0; @@ -231,7 +263,10 @@ int ble_log_compressed_hex_printv(uint8_t source, uint32_t log_index, size_t arg return 0; } - ble_log_compressed_hex_print_internal(mgmt, log_index, args_cnt, args); + if (ble_log_compressed_hex_print_internal(mgmt, log_index, args_cnt, args) != 0) { + ble_compressed_log_buffer_free(mgmt); + return 0; + } ble_compressed_log_output(source, mgmt->buffer, mgmt->idx); ble_compressed_log_buffer_free(mgmt); return 0; @@ -246,12 +281,19 @@ int ble_log_compressed_hex_print(uint8_t source, uint32_t log_index, size_t args } if (args_cnt == 0) { - ble_log_cp_push_u8(mgmt, LOG_HEADER(LOG_TYPE_HEX_ARGS, 0)); - ble_log_cp_push_u16(mgmt, log_index); + if (ble_log_cp_push_u8(mgmt, LOG_HEADER(LOG_TYPE_HEX_ARGS, 0)) != 0 || + ble_log_cp_push_u16(mgmt, log_index) != 0) { + ble_compressed_log_buffer_free(mgmt); + return 0; + } } else { va_list args; va_start(args, args_cnt); - ble_log_compressed_hex_print_internal(mgmt, log_index, args_cnt, args); + if (ble_log_compressed_hex_print_internal(mgmt, log_index, args_cnt, args) != 0) { + va_end(args); + ble_compressed_log_buffer_free(mgmt); + return 0; + } va_end(args); } @@ -276,9 +318,13 @@ int ble_log_compressed_hex_print_buf(uint8_t source, uint32_t log_index, uint8_t return 0; } - ble_log_cp_push_u8(mgmt, LOG_HEADER(LOG_TYPE_HEX_BUF, buf_idx)); - ble_log_cp_push_u16(mgmt, log_index); - ble_log_cp_push_buf(mgmt, buf, len); + if (ble_log_cp_push_u8(mgmt, LOG_HEADER(LOG_TYPE_HEX_BUF, buf_idx)) != 0 || + ble_log_cp_push_u16(mgmt, log_index) != 0 || + ble_log_cp_push_u16(mgmt, (uint16_t)len) != 0 || + ble_log_cp_push_buf(mgmt, buf, (uint16_t)len) != 0) { + ble_compressed_log_buffer_free(mgmt); + return 0; + } ble_compressed_log_output(source, mgmt->buffer, mgmt->idx); ble_compressed_log_buffer_free(mgmt); return 0; diff --git a/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c b/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c index 41f7975938c..16e35d97be0 100644 --- a/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c +++ b/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c @@ -17,6 +17,8 @@ #define BLE_LOG_SPI_BUS SPI2_HOST #define BLE_LOG_SPI_MAX_TRANSFER_SIZE (10240) #define BLE_LOG_SPI_TRANS_ITVL_MIN_US (30) +#define BLE_LOG_SPI_DMA_ALIGN_BYTES (4U) +#define BLE_LOG_SPI_ALIGN_LOG_PERIOD (256U) /* VARIABLE */ BLE_LOG_STATIC bool prph_inited = false; @@ -175,11 +177,26 @@ void ble_log_prph_trans_deinit(ble_log_prph_trans_t **trans) BLE_LOG_IRAM_ATTR void ble_log_prph_send_trans(ble_log_prph_trans_t *trans) { spi_transaction_t *spi_trans = (spi_transaction_t *)trans->ctx; + uint16_t tx_len = trans->pos; + + /* + * SPI slave DMA requires transaction length to be 4-byte aligned. + * Pad trailing bytes with zero to reduce transport loss on slave side. + */ + uint16_t aligned_len = (uint16_t)((tx_len + (BLE_LOG_SPI_DMA_ALIGN_BYTES - 1U)) & + ~(BLE_LOG_SPI_DMA_ALIGN_BYTES - 1U)); + if (aligned_len != tx_len) { + uint16_t pad_len = (uint16_t)(aligned_len - tx_len); + if (aligned_len <= trans->size) { + BLE_LOG_MEMSET(trans->buf + tx_len, 0, pad_len); + tx_len = aligned_len; + } + } /* CRITICAL: * Bytes to bits length conversion is required for tx, and rxlength must be * cleared regardless of whether it is used for rx as per SPI master driver */ - spi_trans->length = (trans->pos << 3); + spi_trans->length = (tx_len << 3); spi_trans->rxlength = 0; if (spi_device_queue_trans(dev_handle, spi_trans, 0) != ESP_OK) { ble_log_lbm_t *lbm = (ble_log_lbm_t *)trans->owner; diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index df52b386a0c..44a5bbb58c2 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit df52b386a0cc78997c2252ce034625212c35ca44 +Subproject commit 44a5bbb58c22b3b6ad2b7773fc96b3a371fa22e6 diff --git a/components/bt/test_apps/basic_unit_test/README.md b/components/bt/test_apps/basic_unit_test/README.md index fc828476cbb..1036c120a1e 100644 --- a/components/bt/test_apps/basic_unit_test/README.md +++ b/components/bt/test_apps/basic_unit_test/README.md @@ -1,5 +1,5 @@ | Supported Targets | ESP32 | ESP32-C3 | -| ----------------- | ----- | -------- | +| ----------------- | ----- | -------- | # `bt` component unit tests diff --git a/examples/bluetooth/nimble/ble_spi_slave/CMakeLists.txt b/examples/bluetooth/nimble/ble_spi_slave/CMakeLists.txt new file mode 100644 index 00000000000..43c7d201ced --- /dev/null +++ b/examples/bluetooth/nimble/ble_spi_slave/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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.22) + +# list(APPEND sdkconfig_defaults) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +# idf_build_set_property(MINIMAL_BUILD ON) +project(ble_spi_slave) diff --git a/examples/bluetooth/nimble/ble_spi_slave/README.md b/examples/bluetooth/nimble/ble_spi_slave/README.md new file mode 100644 index 00000000000..15d1a544b1c --- /dev/null +++ b/examples/bluetooth/nimble/ble_spi_slave/README.md @@ -0,0 +1,111 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | + +# NimBLE SPI Slave Receiver Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview + +This example configures the device as an **SPI slave receiver** and prints received data to UART in hexadecimal format. + +It is intended to work with a peer device acting as an SPI master (for example, a NimBLE BLE Log sender). Data is received on SPI and projected to the serial monitor, which makes this example useful for bring-up and transport validation. + +### What this example does + +- Initializes SPI slave mode (`SPI2_HOST`) +- Uses MOSI/SCLK/CS lines to receive data in half-duplex style +- Keeps MISO disabled (`-1`) +- Receives transactions in 1024-byte DMA buffers +- Prints received bytes to UART in 32-byte lines (`%02X` format) + +## Hardware behavior + +The receive pins are configured from Kconfig symbols used by this example: + +- `CONFIG_BT_BLE_LOG_SPI_OUT_MOSI_IO_NUM` +- `CONFIG_BT_BLE_LOG_SPI_OUT_SCLK_IO_NUM` +- `CONFIG_BT_BLE_LOG_SPI_OUT_CS_IO_NUM` + +Defined in [main/ble_spi_slave.h](main/ble_spi_slave.h). + +The example enables pull-ups on MOSI/SCLK/CS to reduce false activity when no master is connected. + +## Hardware connection + +Connect your SPI master device to the example target: + +- Master MOSI -> Slave MOSI +- Master SCLK -> Slave SCLK +- Master CS -> Slave CS +- GND <-> GND + +Notes: + +- MISO is not used by this example. +- SPI mode is fixed to mode 0 in current code. +- Ensure both devices share a common ground. + +## How to Use Example + +Before project configuration and build, set the target: + +```bash +idf.py set-target +``` + +### Configure the project + +Open configuration menu: + +```bash +idf.py menuconfig +``` + +Configure SPI pin assignments used by this example via Bluetooth/BLE Log SPI output configuration (MOSI/SCLK/CS), so they match your wiring. + +### Build and flash + +```bash +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type `Ctrl-]`.) + +## Expected output + +When the SPI master sends data, this example prints the received bytes as hexadecimal text, for example: + +```text +08 00 07 4F 01 00 CC CA 18 00 ... +``` + +Output is line-broken every 32 bytes for readability in the monitor. + +## Typical use with BLE Log pipelines + +This receiver can be used as the SPI-side capture endpoint in BLE Log transport testing: + +1. Master device sends BLE Log frame bytes over SPI. +2. This example receives and prints them on UART. +3. Host tools can capture UART hex and decode with BLE log decompression scripts. + +## Limitations + +- This example does not decode BLE payloads; it only receives and prints raw bytes. +- Transaction buffer size is fixed at 1024 bytes in current source. + +## Troubleshooting + +- **No data printed** + - Verify wiring and shared ground. + - Verify pin configuration in menuconfig matches your board wiring. + - Verify SPI master is actively transmitting and uses mode 0. + +- **Corrupted or unstable output** + - Check signal integrity and clock speed. + - Confirm pull-ups and board-level electrical compatibility. + +- **Build errors related to drivers** + - Ensure SPI/GPIO driver components are enabled (already declared in this example component dependencies). + diff --git a/examples/bluetooth/nimble/ble_spi_slave/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_spi_slave/main/CMakeLists.txt new file mode 100644 index 00000000000..6ee215eca17 --- /dev/null +++ b/examples/bluetooth/nimble/ble_spi_slave/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + REQUIRES esp_driver_spi esp_driver_gpio + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_spi_slave/main/ble_spi_slave.h b/examples/bluetooth/nimble/ble_spi_slave/main/ble_spi_slave.h new file mode 100644 index 00000000000..dadd33b74c9 --- /dev/null +++ b/examples/bluetooth/nimble/ble_spi_slave/main/ble_spi_slave.h @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +// Please update the following configuration according to your HardWare spec +#define RCV_HOST SPI2_HOST + +#define GPIO_MOSI CONFIG_BT_BLE_LOG_SPI_OUT_MOSI_IO_NUM +#define GPIO_MISO -1 +#define GPIO_SCLK CONFIG_BT_BLE_LOG_SPI_OUT_SCLK_IO_NUM +#define GPIO_CS CONFIG_BT_BLE_LOG_SPI_OUT_CS_IO_NUM diff --git a/examples/bluetooth/nimble/ble_spi_slave/main/main.c b/examples/bluetooth/nimble/ble_spi_slave/main/main.c new file mode 100644 index 00000000000..0edb49ecd97 --- /dev/null +++ b/examples/bluetooth/nimble/ble_spi_slave/main/main.c @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" +#include "driver/spi_slave.h" +#include "driver/gpio.h" + +#include "ble_spi_slave.h" + +/* +SPI receiver (slave) example. + +This example is supposed to work together with a NimBLE module acting as SPI master. +It uses the standard SPI pins (MISO, MOSI, SCLK, CS) to transmit logs over in a +half-duplex fashion, that is, the master puts data on the MOSI pin, while the MISO pins +remain inactive. +*/ + +//Dummy callback called after a transaction is queued and ready for pickup by master. +static void my_post_setup_cb(spi_slave_transaction_t *trans) +{ + return; +} + +//Dummy callback called after transaction is sent/received. +static void my_post_trans_cb(spi_slave_transaction_t *trans) +{ + return; +} + +//Main application +void app_main(void) +{ + int n = 0; + esp_err_t ret; + spi_slave_transaction_t t = {0}; + + //Configuration for the SPI bus + spi_bus_config_t buscfg = { + .mosi_io_num = GPIO_MOSI, + .miso_io_num = -1, + .sclk_io_num = GPIO_SCLK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + }; + + //Configuration for the SPI slave interface + spi_slave_interface_config_t slvcfg = { + .mode = 0, + .spics_io_num = GPIO_CS, + .queue_size = 3, + .flags = 0, + .post_setup_cb = my_post_setup_cb, + .post_trans_cb = my_post_trans_cb + }; + + //Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected. + gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY); + + /** + * The default drive capability on esp32 is GPIO_DRIVE_CAP_2 (~20 mA). + * When connecting master devices that uses a source/sink current lower or higher than GPIO_DRIVER_CAP_DEFAULT. + * Using a drive strength that does not match the requirements of the connected device can cause issues + * such as unreliable switching, or damage to the GPIO pin or external device. + * + * - GPIO_DRIVE_CAP_0: ~5 mA + * - GPIO_DRIVE_CAP_1: ~10 mA + * - GPIO_DRIVE_CAP_2: ~20 mA + * - GPIO_DRIVE_CAP_3: ~40 mA + + gpio_set_drive_capability(GPIO_MOSI, GPIO_DRIVE_CAP_3); + gpio_set_drive_capability(GPIO_SCLK, GPIO_DRIVE_CAP_3); + gpio_set_drive_capability(GPIO_CS, GPIO_DRIVE_CAP_3); + + **/ + + //Initialize SPI slave interface + ret = spi_slave_initialize(RCV_HOST, &buscfg, &slvcfg, SPI_DMA_CH_AUTO); + assert(ret == ESP_OK); + + // For SPI DMA, buffers should be word-aligned. + WORD_ALIGNED_ATTR uint8_t recvbuf[1024]; + + while (1) { + //Clear receive buffer, set send buffer to something sane + memset(recvbuf, 0x00, 1024); + + //Set up a transaction of 1024 bytes to receive + t.length = 1024 * 8; + t.tx_buffer = NULL; + t.rx_buffer = recvbuf; + + /* This call enables the SPI slave interface to receive to the recvbuf. The transaction is + * initialized by the SPI master, however, so it will not actually happen until the master starts a hardware transaction + * by pulling CS low and pulsing the clock etc. + */ + ret = spi_slave_transmit(RCV_HOST, &t, portMAX_DELAY); + + /* Get the actual number of bytes received */ + int rcv_bytes = t.trans_len / 8; + if (rcv_bytes == 0) { + rcv_bytes = 1024; // Fallback if hardware didn't report exact length + } + + /* Print the binary buffer in safe 32-byte chunks */ + for (int i = 0; i < rcv_bytes; i++) { + // Print the raw byte (ignoring null-terminators) + printf("%02X ", recvbuf[i]); + + // Break the line to prevent ESP-IDF terminal truncation + if ((i + 1) % 32 == 0 || i == rcv_bytes - 1) { + printf("\n"); + } + } + + n++; + } + +} diff --git a/examples/bluetooth/nimble/ble_spi_slave/sdkconfig.defaults b/examples/bluetooth/nimble/ble_spi_slave/sdkconfig.defaults new file mode 100644 index 00000000000..c3ce04be2da --- /dev/null +++ b/examples/bluetooth/nimble/ble_spi_slave/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED=y