Merge branch 'feat/nimble_logs_compression' into 'master'

feat(nimble): Support NimBLE log compression and decompression over SPI

See merge request espressif/esp-idf!46967
This commit is contained in:
Rahul Tank
2026-04-22 08:02:05 +05:30
19 changed files with 2132 additions and 51 deletions
@@ -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)
@@ -0,0 +1,111 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H4 | 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 <chip_name>
```
### 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).
@@ -0,0 +1,3 @@
idf_component_register(SRCS "main.c"
REQUIRES esp_driver_spi esp_driver_gpio
INCLUDE_DIRS ".")
@@ -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
@@ -0,0 +1,131 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#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++;
}
}
@@ -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