I'm looking to set up a UART-BLE passthrough for high data rate transmissions (~7500 bytes/s). I've been looking into the peripheral_uart example script but it can't keep up with the transmission speed. I'm sending a count up to 1000 over the RX pin and seeing about half lost on the BLE output. I haven't been able to identify whether the issue is with the UART or BLE.
BLE output on sending 0-1000:

I've removed all of the code relating to BLE security as this is not necessary. I have increased the size of the UART RX buffer. I tried to increase BLE MTU size to 251 in the project config but this is disallowed as its being overwritten somewhere else.
Unfortunately for our particular use, case flow control is not an option.
Peripheral_uart with modifications:
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/** @file
* @brief Nordic UART Bridge Service (NUS) sample
*/
#include "uart_async_adapter.h"
#include <zephyr/types.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <soc.h>
#include <sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <bluetooth/services/nus.h>
#include <dk_buttons_and_leds.h>
#include <zephyr/settings/settings.h>
#include <stdio.h>
#include <zephyr/logging/log.h>
#define LOG_MODULE_NAME peripheral_uart
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#define STACKSIZE CONFIG_BT_NUS_THREAD_STACK_SIZE
#define PRIORITY 1
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
#define RUN_STATUS_LED DK_LED1
#define RUN_LED_BLINK_INTERVAL 1000
#define CON_STATUS_LED DK_LED2
#define KEY_PASSKEY_ACCEPT DK_BTN1_MSK
#define KEY_PASSKEY_REJECT DK_BTN2_MSK
#define UART_BUF_SIZE CONFIG_BT_NUS_UART_BUFFER_SIZE
#define UART_WAIT_FOR_BUF_DELAY K_MSEC(5)
#define UART_WAIT_FOR_RX 20//PCONFIG_BT_NUS_UART_RX_WAIT_TIME
#define MIN_CONN_INTERVAL 50
#define MAX_CONN_INTERVAL 400
#define MTU_SIZE NRF_SDH_BLE
static K_SEM_DEFINE(ble_init_ok, 0, 1);
static struct bt_conn *current_conn;
static struct bt_conn *auth_conn;
static const struct device *uart = DEVICE_DT_GET(DT_CHOSEN(nordic_nus_uart));
static struct k_work_delayable uart_work;
struct uart_data_t {
void *fifo_reserved;
uint8_t data[80];
uint16_t len;
};
static K_FIFO_DEFINE(fifo_uart_tx_data);
static K_FIFO_DEFINE(fifo_uart_rx_data);
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};
static const struct bt_data sd[] = {
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_VAL),
};
#if CONFIG_BT_NUS_UART_ASYNC_ADAPTER
UART_ASYNC_ADAPTER_INST_DEFINE(async_adapter);
#else
static const struct device *const async_adapter;
#endif
const struct uart_config uart_cfg = { //serial communications settings
.baudrate = 115200,
.parity = UART_CFG_PARITY_NONE,
.stop_bits = UART_CFG_STOP_BITS_1,
.data_bits = UART_CFG_DATA_BITS_8,
.flow_ctrl = UART_CFG_FLOW_CTRL_NONE
};
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
ARG_UNUSED(dev);
static size_t aborted_len;
struct uart_data_t *buf;
static uint8_t *aborted_buf;
static bool disable_req;
switch (evt->type) {
case UART_TX_DONE:
LOG_DBG("UART_TX_DONE");
if ((evt->data.tx.len == 0) ||
(!evt->data.tx.buf)) {
return;
}
if (aborted_buf) {
buf = CONTAINER_OF(aborted_buf, struct uart_data_t,
data);
aborted_buf = NULL;
aborted_len = 0;
} else {
buf = CONTAINER_OF(evt->data.tx.buf, struct uart_data_t,
data);
}
k_free(buf);
buf = k_fifo_get(&fifo_uart_tx_data, K_NO_WAIT);
if (!buf) {
return;
}
if (uart_tx(uart, buf->data, buf->len, SYS_FOREVER_MS)) {
LOG_WRN("Failed to send data over UART");
}
break;
case UART_RX_RDY:
LOG_DBG("UART_RX_RDY");
buf = CONTAINER_OF(evt->data.rx.buf, struct uart_data_t, data);
buf->len += evt->data.rx.len;
if (disable_req) {
return;
}
if ((evt->data.rx.buf[buf->len - 1] == '\n') ||
(buf->len == 14)) {
disable_req = true;
uart_rx_disable(uart);
}
break;
case UART_RX_DISABLED:
LOG_DBG("UART_RX_DISABLED");
disable_req = false;
buf = k_malloc(sizeof(*buf));
if (buf) {
buf->len = 0;
} else {
LOG_WRN("Not able to allocate UART receive buffer");
k_work_reschedule(&uart_work, UART_WAIT_FOR_BUF_DELAY);
return;
}
uart_rx_enable(uart, buf->data, sizeof(buf->data),
UART_WAIT_FOR_RX);
break;
case UART_RX_BUF_REQUEST:
LOG_DBG("UART_RX_BUF_REQUEST");
buf = k_malloc(sizeof(*buf));
if (buf) {
buf->len = 0;
uart_rx_buf_rsp(uart, buf->data, sizeof(buf->data));
} else {
LOG_WRN("Not able to allocate UART receive buffer");
}
break;
case UART_RX_BUF_RELEASED:
LOG_DBG("UART_RX_BUF_RELEASED");
buf = CONTAINER_OF(evt->data.rx_buf.buf, struct uart_data_t,
data);
if (buf->len > 0) {
k_fifo_put(&fifo_uart_rx_data, buf);
//printk("RX data: %s\n", buf->data);
} else {
k_free(buf);
}
break;
case UART_TX_ABORTED:
LOG_DBG("UART_TX_ABORTED");
if (!aborted_buf) {
aborted_buf = (uint8_t *)evt->data.tx.buf;
}
aborted_len += evt->data.tx.len;
buf = CONTAINER_OF(aborted_buf, struct uart_data_t,
data);
uart_tx(uart, &buf->data[aborted_len],
buf->len - aborted_len, SYS_FOREVER_MS);
break;
default:
break;
}
}
static void uart_work_handler(struct k_work *item)
{
struct uart_data_t *buf;
buf = k_malloc(sizeof(*buf));
if (buf) {
buf->len = 0;
} else {
LOG_WRN("Not able to allocate UART receive buffer");
k_work_reschedule(&uart_work, UART_WAIT_FOR_BUF_DELAY);
return;
}
uart_rx_enable(uart, buf->data, sizeof(buf->data), UART_WAIT_FOR_RX);
}
static bool uart_test_async_api(const struct device *dev)
{
const struct uart_driver_api *api =
(const struct uart_driver_api *)dev->api;
return (api->callback_set != NULL);
}
static int uart_init(void)
{
int err;
int pos;
struct uart_data_t *rx;
struct uart_data_t *tx;
uart_configure(uart, &uart_cfg);
if (!device_is_ready(uart)) {
return -ENODEV;
}
if (IS_ENABLED(CONFIG_USB_DEVICE_STACK)) {
err = usb_enable(NULL);
if (err && (err != -EALREADY)) {
LOG_ERR("Failed to enable USB");
return err;
}
}
rx = k_malloc(sizeof(*rx));
if (rx) {
rx->len = 0;
} else {
return -ENOMEM;
}
k_work_init_delayable(&uart_work, uart_work_handler);
if (IS_ENABLED(CONFIG_BT_NUS_UART_ASYNC_ADAPTER) && !uart_test_async_api(uart)) {
/* Implement API adapter */
uart_async_adapter_init(async_adapter, uart);
uart = async_adapter;
}
err = uart_callback_set(uart, uart_cb, NULL);
if (err) {
LOG_ERR("Cannot initialize UART callback");
return err;
}
if (IS_ENABLED(CONFIG_UART_LINE_CTRL)) {
LOG_INF("Wait for DTR");
while (true) {
uint32_t dtr = 0;
uart_line_ctrl_get(uart, UART_LINE_CTRL_DTR, &dtr);
if (dtr) {
break;
}
/* Give CPU resources to low priority threads. */
k_sleep(K_MSEC(100));
}
LOG_INF("DTR set");
err = uart_line_ctrl_set(uart, UART_LINE_CTRL_DCD, 1);
if (err) {
LOG_WRN("Failed to set DCD, ret code %d", err);
}
err = uart_line_ctrl_set(uart, UART_LINE_CTRL_DSR, 1);
if (err) {
LOG_WRN("Failed to set DSR, ret code %d", err);
}
}
tx = k_malloc(sizeof(*tx));
if (tx) {
pos = snprintf(tx->data, sizeof(tx->data),
"Starting Nordic UART service example\r\n");
if ((pos < 0) || (pos >= sizeof(tx->data))) {
k_free(tx);
LOG_ERR("snprintf returned %d", pos);
return -ENOMEM;
}
tx->len = pos;
} else {
return -ENOMEM;
}
err = uart_tx(uart, tx->data, tx->len, SYS_FOREVER_MS);
if (err) {
LOG_ERR("Cannot display welcome message (err: %d)", err);
return err;
}
return uart_rx_enable(uart, rx->data, sizeof(rx->data), UART_WAIT_FOR_RX);
}
static void connected(struct bt_conn *conn, uint8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
if (err) {
LOG_ERR("Connection failed (err %u)", err);
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_INF("Connected %s", addr);
current_conn = bt_conn_ref(conn);
//below code is added to increase MTU size as per throughput example
struct bt_conn_le_data_len_param data_len_param;
data_len_param.tx_max_len = 200; // byte
data_len_param.tx_max_time = 3750; // us
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_INF("Disconnected: %s (reason %u)", addr, reason);
if (auth_conn) {
bt_conn_unref(auth_conn);
auth_conn = NULL;
}
if (current_conn) {
bt_conn_unref(current_conn);
current_conn = NULL;
//dk_set_led_off(CON_STATUS_LED);
}
}
// #ifdef CONFIG_BT_NUS_SECURITY_ENABLED
// static void security_changed(struct bt_conn *conn, bt_security_t level,
// enum bt_security_err err)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// if (!err) {
// LOG_INF("Security changed: %s level %u", addr, level);
// } else {
// LOG_WRN("Security failed: %s level %u err %d", addr,
// level, err);
// }
// }
// #endif
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
// #ifdef CONFIG_BT_NUS_SECURITY_ENABLED
// .security_changed = security_changed,
// #endif
};
// #if defined(CONFIG_BT_NUS_SECURITY_ENABLED)
// static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// LOG_INF("Passkey for %s: %06u", addr, passkey);
// }
// static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// auth_conn = bt_conn_ref(conn);
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// LOG_INF("Passkey for %s: %06u", addr, passkey);
// LOG_INF("Press Button 1 to confirm, Button 2 to reject.");
// }
// static void auth_cancel(struct bt_conn *conn)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// LOG_INF("Pairing cancelled: %s", addr);
// }
// static void pairing_complete(struct bt_conn *conn, bool bonded)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// LOG_INF("Pairing completed: %s, bonded: %d", addr, bonded);
// }
// static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
// {
// char addr[BT_ADDR_LE_STR_LEN];
// bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
// LOG_INF("Pairing failed conn: %s, reason %d", addr, reason);
// }
// static struct bt_conn_auth_cb conn_auth_callbacks = {
// .passkey_display = auth_passkey_display,
// .passkey_confirm = auth_passkey_confirm,
// .cancel = auth_cancel,
// };
// static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
// .pairing_complete = pairing_complete,
// .pairing_failed = pairing_failed
// };
// #else
// static struct bt_conn_auth_cb conn_auth_callbacks;
// #endif
static void bt_receive_cb(struct bt_conn *conn, const uint8_t *const data,
uint16_t len)
{
int err;
char addr[BT_ADDR_LE_STR_LEN] = {0};
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, ARRAY_SIZE(addr));
LOG_INF("Received data from: %s", addr);
for (uint16_t pos = 0; pos != len;) {
struct uart_data_t *tx = k_malloc(sizeof(*tx));
if (!tx) {
LOG_WRN("Not able to allocate UART send data buffer");
return;
}
/* Keep the last byte of TX buffer for potential LF char. */
size_t tx_data_size = sizeof(tx->data) - 1;
if ((len - pos) > tx_data_size) {
tx->len = tx_data_size;
} else {
tx->len = (len - pos);
}
memcpy(tx->data, &data[pos], tx->len);
pos += tx->len;
/* Append the LF character when the CR character triggered
* transmission from the peer.
*/
if ((pos == len) && (data[len - 1] == '\r')) {
tx->data[tx->len] = '\n';
tx->len++;
}
err = uart_tx(uart, tx->data, tx->len, SYS_FOREVER_MS);
if (err) {
k_fifo_put(&fifo_uart_tx_data, tx);
}
}
}
// static struct bt_nus_cb nus_cb = {
// .received = bt_receive_cb,
// };
// void error(void)
// {
// dk_set_leds_state(DK_ALL_LEDS_MSK, DK_NO_LEDS_MSK);
// while (true) {
// /* Spin for ever */
// k_sleep(K_MSEC(1000));
// }
// }
// #ifdef CONFIG_BT_NUS_SECURITY_ENABLED
// static void num_comp_reply(bool accept)
// {
// if (accept) {
// bt_conn_auth_passkey_confirm(auth_conn);
// LOG_INF("Numeric Match, conn %p", (void *)auth_conn);
// } else {
// bt_conn_auth_cancel(auth_conn);
// LOG_INF("Numeric Reject, conn %p", (void *)auth_conn);
// }
// bt_conn_unref(auth_conn);
// auth_conn = NULL;
// }
// void button_changed(uint32_t button_state, uint32_t has_changed)
// {
// uint32_t buttons = button_state & has_changed;
// if (auth_conn) {
// if (buttons & KEY_PASSKEY_ACCEPT) {
// num_comp_reply(true);
// }
// if (buttons & KEY_PASSKEY_REJECT) {
// num_comp_reply(false);
// }
// }
// }
// #endif /* CONFIG_BT_NUS_SECURITY_ENABLED */
// static void configure_gpio(void)
// {
// int err;
// #ifdef CONFIG_BT_NUS_SECURITY_ENABLED
// err = dk_buttons_init(button_changed);
// if (err) {
// LOG_ERR("Cannot init buttons (err: %d)", err);
// }
// #endif /* CONFIG_BT_NUS_SECURITY_ENABLED */
// err = dk_leds_init();
// if (err) {
// LOG_ERR("Cannot init LEDs (err: %d)", err);
// }
// }
void main(void)
{
int blink_status = 0;
int err = 0;
//configure_gpio();
err = uart_init();
if (err) {
//error();
}
// if (IS_ENABLED(CONFIG_BT_NUS_SECURITY_ENABLED)) {
// err = bt_conn_auth_cb_register(&conn_auth_callbacks);
// if (err) {
// printk("Failed to register authorization callbacks.\n");
// return;
// }
// err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
// if (err) {
// printk("Failed to register authorization info callbacks.\n");
// return;
// }
// }
err = bt_enable(NULL);
if (err) {
//error();
}
LOG_INF("Bluetooth initialized");
k_sem_give(&ble_init_ok);
if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}
// err = bt_nus_init(&nus_cb);
// if (err) {
// LOG_ERR("Failed to initialize UART service (err: %d)", err);
// return;
// }
//const struct bt_le_conn_param *param =
bt_conn_le_param_update(current_conn, BT_LE_CONN_PARAM(MIN_CONN_INTERVAL, MAX_CONN_INTERVAL, 0,40));
err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), sd,
ARRAY_SIZE(sd));
if (err) {
LOG_ERR("Advertising failed to start (err %d)", err);
return;
}
}
void ble_write_thread(void)
{
/* Don't go any further until BLE is initialized */
k_sem_take(&ble_init_ok, K_FOREVER);
for (;;) {
/* Wait indefinitely for data to be sent over bluetooth */
struct uart_data_t *buf = k_fifo_get(&fifo_uart_rx_data,
K_FOREVER);
if (bt_nus_send(NULL, buf->data, buf->len)) {
LOG_WRN("Failed to send data over BLE connection");
}
k_free(buf);
}
}
K_THREAD_DEFINE(ble_write_thread_id, STACKSIZE, ble_write_thread, NULL, NULL,
NULL, PRIORITY, 0, 0);
minimal .conf file:
# # Copyright (c) 2021 Nordic Semiconductor # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # # Enable the UART driver CONFIG_UART_ASYNC_API=y CONFIG_NRFX_UARTE0=y CONFIG_SERIAL=y CONFIG_HEAP_MEM_POOL_SIZE=2048 CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_DEVICE_NAME="Nordic_UART_Service" CONFIG_BT_DEVICE_APPEARANCE=833 CONFIG_BT_MAX_CONN=1 CONFIG_BT_MAX_PAIRED=1 # Enable the NUS service CONFIG_BT_NUS=y # Enable bonding CONFIG_BT_SETTINGS=y CONFIG_FLASH=y CONFIG_FLASH_PAGE_LAYOUT=y CONFIG_FLASH_MAP=y CONFIG_NVS=y CONFIG_SETTINGS=y # Enable DK LED and Buttons library CONFIG_DK_LIBRARY=y # Drivers and peripherals CONFIG_I2C=n CONFIG_WATCHDOG=n CONFIG_PINMUX=n CONFIG_SPI=n CONFIG_GPIO=n # Power management CONFIG_PM=n # Interrupts CONFIG_DYNAMIC_INTERRUPTS=n CONFIG_IRQ_OFFLOAD=n # Memory protection CONFIG_THREAD_STACK_INFO=n CONFIG_THREAD_CUSTOM_DATA=n CONFIG_FPU=n # Boot CONFIG_BOOT_BANNER=n CONFIG_BOOT_DELAY=0 # Console CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y CONFIG_STDOUT_CONSOLE=n CONFIG_PRINTK=y CONFIG_EARLY_CONSOLE=n CONFIG_LOG=n # Build CONFIG_SIZE_OPTIMIZATIONS=y # ARM CONFIG_ARM_MPU=n # In order to correctly tune the stack sizes for the threads the following # Configurations can enabled to print the current use: #CONFIG_THREAD_NAME=y #CONFIG_THREAD_ANALYZER=y #CONFIG_THREAD_ANALYZER_AUTO=y #CONFIG_THREAD_ANALYZER_RUN_UNLOCKED=y #CONFIG_THREAD_ANALYZER_USE_PRINTK=y #CONFIG_CONSOLE=y #CONFIG_UART_CONSOLE=y #CONFIG_SERIAL=y #CONFIG_PRINTK=y # Example output of thread analyzer #SDC RX : unused 800 usage 224 / 1024 (21 %) #BT ECC : unused 216 usage 888 / 1104 (80 %) #BT RX : unused 1736 usage 464 / 2200 (21 %) #BT TX : unused 1008 usage 528 / 1536 (34 %) #ble_write_thread_id : unused 688 usage 336 / 1024 (32 %) #sysworkq : unused 1912 usage 136 / 2048 (6 %) #MPSL signal : unused 928 usage 96 / 1024 (9 %) #idle 00 : unused 224 usage 96 / 320 (30 %) #main : unused 568 usage 456 / 1024 (44 %) CONFIG_BT_CTLR_SDC_RX_STACK_SIZE=324 CONFIG_BT_RX_STACK_SIZE=1024 CONFIG_BT_HCI_TX_STACK_SIZE_WITH_PROMPT=y CONFIG_BT_HCI_TX_STACK_SIZE=640 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=512 CONFIG_MPSL_WORK_STACK_SIZE=256 CONFIG_MAIN_STACK_SIZE=640 CONFIG_IDLE_STACK_SIZE=128 CONFIG_ISR_STACK_SIZE=1024 CONFIG_BT_NUS_THREAD_STACK_SIZE=512 # Disable features not needed CONFIG_TIMESLICING=n CONFIG_MINIMAL_LIBC_MALLOC=n CONFIG_ASSERT=n # Disable Bluetooth features not needed CONFIG_BT_DEBUG_NONE=y CONFIG_BT_ASSERT=n CONFIG_BT_DATA_LEN_UPDATE=y CONFIG_BT_PHY_UPDATE=y CONFIG_BT_GATT_CACHING=n CONFIG_BT_GATT_SERVICE_CHANGED=n CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS=n CONFIG_BT_SETTINGS_CCC_LAZY_LOADING=y CONFIG_BT_HCI_VS_EXT=n # Disable Bluetooth controller features not needed CONFIG_BT_CTLR_PRIVACY=n CONFIG_BT_CTLR_PHY_2M=y # Reduce Bluetooth buffers CONFIG_BT_BUF_EVT_DISCARDABLE_COUNT=1 CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=43 CONFIG_BT_BUF_EVT_RX_COUNT=2 CONFIG_BT_CONN_TX_MAX=2 CONFIG_BT_L2CAP_TX_BUF_COUNT=2 CONFIG_BT_CTLR_RX_BUFFERS=1 CONFIG_BT_BUF_ACL_TX_COUNT=3 CONFIG_BT_BUF_ACL_TX_SIZE=27 CONFIG_BT_CTLR_DATA_LENGTH_MAX=27
I'm running nRF connect SDK v1.2.0 in VSCode with the nrf52dk built as nrf52810.
Help is much appreciated.