Программирование ARM Bluetooth SPP: обзор вариантов реализации Sun, October 18 2026  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Bluetooth SPP: обзор вариантов реализации Печать
Добавил(а) microsin   

Bluetooth SPP (Serial Port Profile) — это стандартный профиль Bluetooth, который создает виртуальный последовательный порт (RS-232 / UART) для беспроводной передачи данных.

Основные характеристики и особенности

• Принцип работы: "байт вошел, байт вышел" — устройства обмениваются потоком данных так, будто они соединены обычным проводом.
• Технология: относится к классическому Bluetooth (Bluetooth Classic / v2.1 + EDR), в отличие от BLE (Bluetooth Low Energy), который устроен сложнее.
• Популярные модули: часто используется в микроконтроллерах и хобби-проектах на базе плат Arduino (например, популярные модули HC-05, HC-06 или аналог SPP-C на Ozon [2]).
• Применение: подключение GPS-приемников, сканеров штрих-кодов, RFID-считывателей, настройка встроенных систем и микроконтроллеров [3, 4].
• Приложения для работы: для тестирования и отправки данных со смартфонов используют такие утилиты, как Bluetooth SPP Pro на Uptodown [5] или Bluetooth SPP Manager [3].

[Чем отличаются SPP Acceptor от SPP Initiator?]

SPP Initiator (Инициатор) и SPP Acceptor (Акцептор) — это две противоположные роли устройств в рамках профиля Bluetooth SPP. Главное отличие между ними заключается в том, кто первым начинает (инициирует) процесс создания связи, а кто ожидает подключения [1, 6].

Примером такого разделения ролей служит библиотека Bluetooth SPP API для популярных микроконтроллеров ESP32 [1].

Ключевые различия:

Критерий SPP Initiator SPP Acceptor
Аналогия/роль Клиент (Client) Сервер (Server)
Основная задача Активно ищет другое устройство и отправляет запрос на подключение. Находится в режиме ожидания (прослушивания эфира) и принимает входящие запросы.
Поведение при старте Сканирует эфир в поисках доступных Bluetooth-устройств. Делает себя видимым для обнаружения и запускает службу SPP-сервера.
Осведомленность Должен знать MAC-адрес или имя удаленного устройства, чтобы подключиться. Ему не нужно заранее знать, кто к нему подключится.
Типовые устройства Смартфон, ноутбук, ПК или управляющий микроконтроллер (Master). Беспроводной датчик, Bluetooth-модуль на станке, принтер чеков (Slave).

Важные нюансы:

1. Роли только для этапа подключения: разделение на Initiator и Acceptor критично только до момента установки связи. Как только виртуальный COM-порт открыт, обе стороны становятся абсолютно равноправными — данные могут передаваться в обоих направлениях одновременно (полный дуплекс) [7].

2. Гибкость современных чипов: одно и то же физическое устройство (например, плата на ESP32) в коде можно настроить как в роли Initiator, так и в роли Acceptor. Выбор зависит исключительно от архитектуры вашего проекта. К примеру, если ваш девайс должен сам собирать данные со сторонних датчиков, вы программируете его как Initiator [8].

[Сравнительная таблица модулей и чипов Bluetooth SPP]

Модули беспроводной связи для сквозной передачи данных (UART-over-Bluetooth) прошли путь от классических узкоспециализированных Bluetooth-чипов до мощных комбо-микроконтроллеров.

Важное примечание перед выбором: Apple iOS принципиально не поддерживает профиль Bluetooth Classic SPP без дорогой сертификации MFi. Если ваше устройство должно работать с iPhone, выбирайте современные комбо-чипы и переключайте их в режим BLE (Bluetooth Low Energy) [9]. С Android и ПК (Windows/Linux) без проблем работают все перечисленные в таблице модули.

В таблице собраны как классические модули типа «черный ящик» (не требуют написания кода, управляются AT-командами), так и программируемые SoC-микроконтроллеры (где SPP реализуется программно внутри чипа).

Модуль / семейство чипов Архитектура и тип Версия Bluetooth Поддержка
SPP
Ток (акт. режим) Особенности и альтернативы
HC-05 (на чипе CSR BC417) Классический «мост» UART-to-Bluetooth. Управляется AT-командами. v2.0 + EDR Master и Slave (Инициатор и Акцептор) ~30–40 мА Золотой стандарт для хобби-проектов. Требует вывода EN/KEY для входа в режим настроек. Много подделок на рынке.
HC-06 (на чипе CSR BC417) Упрощенный классический «мост». v2.0 + EDR Только Slave (только Акцептор) ~30–40 мА Входит в режим AT-команд автоматически, если нет активного Bluetooth-соединения. Не может сам подключаться к другим модулям.
JDY-31 / SPP-C (на чипах Beken BK3231 / BK3221 Бюджетные китайские клоны/аналоги HC-05/06. v3.0 SPP (Classic) В основном Slave, некоторые прошивки поддерживают Master. ~8–15 мА (очень экономичные) Значительно дешевле оригинальных HC. Компактнее, имеют лучшую чувствительность (-97 dBm) и дальность. Набор AT-команд часто урезан или изменен.
JDY-33 (двухрежимные чипы Beken) Мультипрофильный мост. v3.0 Classic + BLE 4.0 Slave (для SPP) ~15 мА Идеальная современная замена HC-06. Позволяет одновременно работать и по классическому SPP (с Android/ПК), и по BLE (с iOS).
Espressif (на чипах ESP32, например ESP32-WROOM-32E. Полноценный программируемый SoC (микроконтроллер с Wi-Fi/BT). v4.2 BR/EDR (Classic) + BLE Master и Slave (настраивается программно) ~100–240 мА (из-за мощного CPU) SPP не зашит "из коробки", а поднимается парой строк кода (библиотека BluetoothSerial.h [6]). Избыточен как простой «мост», но идеален, если вся логика устройства живет на самом ESP32.
Bouffalo Lab BL602 (Ai-Thinker WB2) Программируемый SoC на архитектуре RISC-V. Только BLE 5.0 (классический SPP отсутствует) Нет поддержки Classic SPP ~35–50 мА Популярный чип BL602 не поддерживает Bluetooth Classic (BR/EDR). Профиль SPP на нем запустить нельзя. Для передачи данных на нем создается эмуляция последовательного порта через профиль BLE GATT.
Bouffalo Lab BL616 (например, серия Ai-M62, или MoS [15]) Программируемый SoC на 32-битном RISC-V (T-Head E907) с FPU и DSP, до 320 МГц Bluetooth 5.3, двухрежимный (Classic + BLE) SPP поддерживается (благодаря двухрежимному Classic Bluetooth) Wi-Fi приём: ~64 мА; Wi-Fi передача: ~293–374 мА (измерено на модулях BL618) Поддержка Wi-Fi 6, 802.15.4 (Zigbee/Thread). Нет аудиокодека, нет интерфейсов для камеры/LCD. Подходит для чисто беспроводных коммуникаций и мостов Matter.
Bouffalo Lab BL618 (например, серия Ai-M61) Программируемый SoC на 32-битном RISC-V с FPU и DSP, до 320 МГц; дополнительно интегрированы аудиокодек, интерфейс DVP-камеры, MJPEG-кодер, интерфейс LCD (QSPI/DBI/RGB) Bluetooth 5.3, двухрежимный (Classic + BLE) SPP поддерживается (благодаря двухрежимному Classic Bluetooth) Такой же, как у BL616 (приём ~64 мА, передача ~293–374 мА) Надмножество BL616: к тем же беспроводным возможностям добавлены аудио, захват видео и интерфейсы дисплея. Подходит для умных колонок, видеодомофонов, сенсорных панелей управления умным домом и т. п.

Главные выводы для проектирования:

1. Если нужен готовый "переходник без кода" для Android/ПК: берите JDY-31 (если нужно только принимать соединения) или HC-05 (если модуль должен сам инициировать связь) [11, 12].

2. Если нужна универсальность (включая iPhone): берите JDY-33. Он работает по схеме "вставил и забыл", но поддерживает современные телефоны благодаря BLE [8, 12].

3. Если вы создаете устройство с нуля без внешних Arduino: программируйте ESP32. Встроенный стек Bluetooth Classic позволит вам поднять SPP-сервер силами самого процессора, сэкономив на внешних модулях [12, 13].

4. Осторожно с Bouffalo (BL602): этот чип отличный, дешевый и имеет RISC-V ядро, но архитектурно лишен классического Bluetooth. Заменить им HC-05 в проекте, где принимающая сторона жестко требует именно SPP (например, старый софт на ПК или специфический OBD-сканер), не получится [14].

5. Двухрежимность BL616/BL618 позволяет им аппаратно работать с классическим Bluetooth SPP, без необходимости эмулировать последовательный порт через BLE GATT, как в BL602. Основное различие между ними — расширенная поддержка мультимедийных периферийных устройств у BL618.

[Bluetooth SPP Acceptor на ESP32]

Здесь приведен пошаговый пример создания подчиненного устройства SPP на основе примера из ESP-IDF v6.0.3 на хосте разработки Ubuntu 24.04.5 LTS.

1. Сделайте копию папки bt_spp_acceptor (она находится в каталоге examples/bluetooth/bluedroid/classic_bt вашей установки ESP-IDF) в любое удобное место на диске, например ~/myproj.

2. Сделайте этот каталог-копию текущим:

cd ~/myproj/bt_spp_acceptor

3. Запустите команду, настраивающую рабочую среду ESP-IDF (чтобы работали команды idf.py):

. ~/esp/v6.0.3/export.sh

4. Установите для проекта тип чипа (поддерживается только чип ESP32):

idf.py set-target esp32

5. Скомпилируйте проект:

idf.py build

6. Подключите вашу плату через USB. Например это может быть плата разработчика ESP32-WROOM-32D. С помощью команды dmesg определите имя устройства, через которое плата определилась в операционной системе:

sudo dmesg -w | grep tty

Команда выведет что-то наподобие:

[261899.374353] usb 3-1.2: cp210x converter now attached to ttyUSB2

7. Залейте программу в память чипа и подключитесь к монитору программы одной командой (здесь ttyUSB2 это имя устройства, которое вы определили на шаге 6):

idf.py flash monitor -p /dev/ttyUSB2

Вы увидите примерно такой лог:

I (27) boot: ESP-IDF v6.0.3 2nd stage bootloader
I (27) boot: compile time Oct 15 2026 07:32:01
I (28) boot: Multicore bootloader
I (29) boot: chip revision: v3.1
I (32) boot.esp32: SPI Speed      : 40MHz
I (35) boot.esp32: SPI Mode       : DIO
I (39) boot.esp32: SPI Flash Size : 2MB
I (42) boot: Enabling RNG early entropy source...
I (47) boot: Partition Table:
I (49) boot: ## Label            Usage          Type ST Offset   Length
I (56) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (62) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (69) boot:  2 factory          factory app      00 00 00010000 00100000
I (75) boot: End of partition table
I (79) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=1afe4h (110564) map
I (126) esp_image: segment 1: paddr=0002b00c vaddr=3ffbdb60 size=04eb8h ( 20152) load
I (134) esp_image: segment 2: paddr=0002fecc vaddr=40080000 size=0014ch (   332) load
I (134) esp_image: segment 3: paddr=00030020 vaddr=400d0020 size=60298h (393880) map
I (281) esp_image: segment 4: paddr=000902c0 vaddr=4008014c size=15848h ( 88136) load
I (318) esp_image: segment 5: paddr=000a5b10 vaddr=50000000 size=00028h (    40) load
I (329) boot: Loaded app from partition at offset 0x10000
I (329) boot: Disabling RNG early entropy source...
I (340) cpu_start: Multicore app
I (348) cpu_start: GPIO 3 and 1 are used as console UART I/O pins
I (348) cpu_start: Pro cpu start user code
I (348) cpu_start: cpu freq: 160000000 Hz
I (350) app_init: Application information:
I (354) app_init: Project name:     bt_spp_acceptor_demo
I (359) app_init: App version:      1
I (362) app_init: Compile time:     Oct 15 2026 07:31:54
I (367) app_init: ELF file SHA256:  bef059553...
I (372) app_init: ESP-IDF:          v6.0.3
I (376) efuse_init: Min chip rev:     v0.0
I (379) efuse_init: Max chip rev:     v3.99
I (383) efuse_init: Chip rev:         v3.1
I (388) heap_init: Initializing. RAM available for dynamic allocation:
I (394) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (399) heap_init: At 3FFB6388 len 00001C78 (7 KiB): DRAM
I (404) heap_init: At 3FFB9A20 len 00004108 (16 KiB): DRAM
I (409) heap_init: At 3FFC8B48 len 000174B8 (93 KiB): DRAM
I (414) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (419) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (425) heap_init: At 40095994 len 0000A66C (41 KiB): IRAM
W (432) spi_flash: Detected boya flash chip but using generic driver.
 For optimal functionality, enable `SPI_FLASH_SUPPORT_BOYA_CHIP` in menuconfig
I (443) spi_flash: detected chip: generic
I (447) spi_flash: flash io: dio
W (450) spi_flash: Detected size(4096k) larger than the size in the binary
 image header(2048k). Using the size in the binary image header.
I (463) coexist: coex firmware version: a830d28
I (467) main_task: Started on CPU0
I (467) main_task: Calling app_main()
I (517) BTDM_INIT: BT controller compile version [4a0f94d]
I (527) BTDM_INIT: Using main XTAL as clock source
I (527) BTDM_INIT: Bluetooth MAC: 08:a6:f7:22:56:02
I (527) phy_init: phy_version 4863,a3a4459,Oct 28 2025,14:30:06
W (527) phy_init: failed to load RF calibration data (0x1102),
 falling back to full calibration
I (607) phy_init: Saving new calibration data due to checksum failure
 or outdated calibration data, mode(2)
I (1177) SPP_ACCEPTOR_DEMO: ESP_SPP_INIT_EVT
I (1187) SPP_ACCEPTOR_DEMO: ESP_SPP_START_EVT handle:129 sec_id:55 scn:1
I (1187) SPP_ACCEPTOR_DEMO: event: 10
I (1197) SPP_ACCEPTOR_DEMO: Own address:[08:a6:f7:22:56:02]
I (1197) main_task: Returned from app_main()

8. Зайдите в настройки смартфона, раздел Bluetooth. Запустите сканирование, и вы увидите устройство ESP_SPP_ACCEPTOR. При подключении к этому устройству будет выводиться сообщение:

W (172967) BT_HCI: hcif conn complete: hdl 0x80, st 0x0
W (172987) BT_HCI: hcif link supv_to changed: hdl 0x80, supv_to 8000
I (172997) SPP_ACCEPTOR_DEMO: event: 16
W (173367) BT_APPL: new conn_srvc id:26, app_id:255
I (173367) SPP_ACCEPTOR_DEMO: ESP_SPP_SRV_OPEN_EVT status:0 handle:129,
 rem_bda:[d4:a3:65:e7:b6:19]
W (178387) BT_HCI: hcif mode change: hdl 0x80, mode 2, intv 792, status 0x0
I (178387) SPP_ACCEPTOR_DEMO: ESP_BT_GAP_MODE_CHG_EVT mode:2 bda:[d4:a3:65:e7:b6:19]

При отключении выведется:

W (205497) BT_RFCOMM: port_rfc_closed RFCOMM connection in server:1 state 2 closed: Closed (res: 19)
I (205497) SPP_ACCEPTOR_DEMO: ESP_SPP_CLOSE_EVT status:0 handle:129 close_by_remote:1
W (207467) BT_HCI: hcif mode change: hdl 0x80, mode 0, intv 0, status 0x0
I (207467) SPP_ACCEPTOR_DEMO: ESP_BT_GAP_MODE_CHG_EVT mode:0 bda:[d4:a3:65:e7:b6:19]
W (207487) BT_RFCOMM: rfc_find_lcid_mcb LCID reused LCID:0x41 current:0x0
W (207487) BT_RFCOMM: RFCOMM_DisconnectInd LCID:0x41
W (211487) BT_HCI: hci cmd send: disconnect: hdl 0x80, rsn:0x13
W (211567) BT_HCI: hcif disc complete: hdl 0x80, rsn 0x16 dev_find 1
I (211567) SPP_ACCEPTOR_DEMO: event: 17

Мост SPP - TTL UART. Пока что этот код чисто демонстрационный. В этом проекте UART вообще не используется. Каталог main/ содержит только стандартный пример ESP-IDF bt_spp_acceptor (SPP-сервер по Bluetooth Classic). Мост Bluetooth SPP ↔ TTL UART пока не реализован.

Например, если в него передавать через соединение Bluetooth какие-нибудь данные, то в лог будут просто выводиться сообщения наподобие следующих:

I (68907) SPP_ACCEPTOR_DEMO: speed(57.222420s ~ 68.561256s): 0.000706 kbit/s
I (72447) SPP_ACCEPTOR_DEMO: speed(68.561256s ~ 72.106529s): 0.006770 kbit/s
I (76207) SPP_ACCEPTOR_DEMO: speed(72.106529s ~ 75.867828s): 0.014888 kbit/s
I (78447) SPP_ACCEPTOR_DEMO: speed(75.867828s ~ 78.104080s): 0.025042 kbit/s
I (81427) SPP_ACCEPTOR_DEMO: speed(78.104080s ~ 81.087852s): 0.016087 kbit/s

Что делает текущий main.c примера:

1. Поднимает Bluetooth Classic + Bluedroid, стартует SPP-сервер (SPP_SERVER).
2. В callback ESP_SPP_DATA_IND_EVT только логирует принятые данные (ESP_LOG_BUFFER_HEX) или считает скорость (print_speed()), а выводит это в USB-UART консоль через ESP_LOG*.
3. Зависимости в main/CMakeLists.txt: PRIV_REQUIRES bt nvs_flash — драйвера driver/uart здесь нет.

Чтобы проект работал как мост SPP - TTL UART, необходимо в код добавить изменения:

- Добавить driver/uart (uart_driver_install, uart_param_config, uart_set_pin), задать пины явно — uart_set_pin(UART_NUM_2, 17, 16, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE).
- В ESP_SPP_DATA_IND_EVT писать param->data_ind.data в UART (uart_write_bytes), а данные из UART читать отдельной задачей (uart_read_bytes) и отправлять через esp_spp_write.
- Не использовать блокирующие ESP_LOG/printf внутри callback на высокой скорости — это тормозит стек Bluetooth (об этом же предупреждает README в разделе trouble shooting).
- При необходимости — аппаратный flow control (RTS/CTS) на дополнительных GPIO.
- UART0 с логами оставить для отладки, чтобы не потерять idf.py monitor.

Вот полный пример кода main.c, где введены эти изменения (аппаратный flow control RTS/CTS не используется, настройки UART2 стандартные, TX=GPIO17, RX=GPIO16, скорость 9600 бод):

/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include < stdint.h>
#include < string.h>
#include < stdbool.h>
#include < stdio.h>
#include < inttypes.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_bt_api.h"
#include "esp_bt_device.h"
#include "esp_spp_api.h"
#include "driver/uart.h"

#include "time.h"
#include "sys/time.h"

#define SPP_TAG "SPP_ACCEPTOR_DEMO"
#define SPP_SERVER_NAME "SPP_SERVER"
#define SPP_SHOW_DATA 0
#define SPP_SHOW_SPEED 1
// Выберите, что отображать: данные (0) или скорость (1):
#define SPP_SHOW_MODE SPP_SHOW_SPEED

// Настройки UART для моста Bluetooth SPP < - > TTL UART:
// UART2, порты TX/RX, 9600 8N1, no flow control
#define UART_BRIDGE_PORT UART_NUM_2
#define UART_BRIDGE_TX_GPIO 17
#define UART_BRIDGE_RX_GPIO 16
#define UART_BRIDGE_BAUD_RATE 9600
#define UART_BRIDGE_RX_BUF_SIZE 1024
#define UART_BRIDGE_TX_BUF_SIZE 1024
#define UART_BRIDGE_CHUNK 256
#define UART_BRIDGE_QUEUE_LEN 8

typedef struct {
uint8_t data[UART_BRIDGE_CHUNK];
size_t len; } uart_bridge_item_t;

static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME;
static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB;
static const bool esp_spp_enable_l2cap_ertm = true;

static struct timeval time_new, time_old;
static long data_num = 0;

static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE;
static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE;

static QueueHandle_t spp_to_uart_queue = NULL;
static uint32_t spp_conn_handle = 0;
static volatile bool spp_connected = false;
static volatile bool spp_congested = false;

static char *bda2str(uint8_t * bda, char *str, size_t size) {
if (bda == NULL || str == NULL || size < 18) {
return NULL;
}

uint8_t *p = bda;
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
p[0], p[1], p[2], p[3], p[4], p[5]);
return str; }

static void print_speed(void) {
float time_old_s = time_old.tv_sec + time_old.tv_usec / 1000000.0;
float time_new_s = time_new.tv_sec + time_new.tv_usec / 1000000.0;
float time_interval = time_new_s - time_old_s;
float speed = data_num * 8 / time_interval / 1000.0;
ESP_LOGI(SPP_TAG, "speed(%fs ~ %fs): %f kbit/s" , time_old_s, time_new_s, speed);
data_num = 0;
time_old.tv_sec = time_new.tv_sec;
time_old.tv_usec = time_new.tv_usec; }

static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
char bda_str[18] = {0};
switch (event) {
case ESP_SPP_INIT_EVT:
if (param->init.status == ESP_SPP_SUCCESS) {
ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT");
esp_spp_start_srv(sec_mask, role_slave, 0, SPP_SERVER_NAME);
} else {
ESP_LOGE(SPP_TAG, "ESP_SPP_INIT_EVT status:%d", param->init.status);
}
break;
case ESP_SPP_DISCOVERY_COMP_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT");
break;
case ESP_SPP_OPEN_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT handle:%"PRIu32, param->open.handle);
spp_conn_handle = param->open.handle;
spp_congested = false;
spp_connected = true;
break;
case ESP_SPP_CLOSE_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT status:%d handle:%"PRIu32" close_by_remote:%d",
param->close.status, param->close.handle, param->close.async);
spp_connected = false;
spp_congested = false;
spp_conn_handle = 0;
break;
case ESP_SPP_START_EVT:
if (param->start.status == ESP_SPP_SUCCESS) {
ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT handle:%"PRIu32" sec_id:%d scn:%d",
param->start.handle, param->start.sec_id, param->start.scn);
esp_bt_gap_set_device_name(local_device_name);
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
} else {
ESP_LOGE(SPP_TAG, "ESP_SPP_START_EVT status:%d", param->start.status);
}
break;
case ESP_SPP_CL_INIT_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT");
break;
case ESP_SPP_DATA_IND_EVT:
/* Перенаправление данных от Bluetooth SPP в the TTL UART */
if (param->data_ind.len > 0) {
const uint8_t *p = param->data_ind.data;
size_t remaining = param->data_ind.len;
while (remaining > 0) {
uart_bridge_item_t item;
item.len = remaining > UART_BRIDGE_CHUNK ? UART_BRIDGE_CHUNK : remaining;
memcpy(item.data, p, item.len);
if (xQueueSend(spp_to_uart_queue, &item, 0) != pdTRUE) {
ESP_LOGW(SPP_TAG, "UART TX queue full, dropped %u bytes", (unsigned)item.len);
}
p += item.len;
remaining -= item.len;
}
}
#if (SPP_SHOW_MODE == SPP_SHOW_DATA)
/*
* Здесь мы отображаем только те данные, длина которых меньше 128.
* Если вы хотите выводить данные в печать, а скорость передачи данных высокая,
* настоятельно рекомендуется обрабатывать их в отдельной задаче приложения
* с более низким приоритетом, а не напрямую в этом callback. Поскольку вывод
* printf занимает слишком много времени, это может заблокировать стек Bluetooth,
* а также повлиять на пропускную способность!
*/
ESP_LOGI(SPP_TAG, "ESP_SPP_DATA_IND_EVT len:%d handle:%"PRIu32,
param->data_ind.len, param->data_ind.handle);
if (param->data_ind.len < 128) {
ESP_LOG_BUFFER_HEX("", param->data_ind.data, param->data_ind.len);
}
#else
gettimeofday(&time_new, NULL);
data_num += param->data_ind.len;
if (time_new.tv_sec - time_old.tv_sec >= 3) {
print_speed();
}
#endif
break;
case ESP_SPP_CONG_EVT:
spp_congested = param->cong.cong;
ESP_LOGI(SPP_TAG, "ESP_SPP_CONG_EVT cong:%d", param->cong.cong);
break;
case ESP_SPP_WRITE_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_WRITE_EVT");
break;
case ESP_SPP_SRV_OPEN_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT status:%d handle:%"PRIu32", rem_bda:[%s]",
param->srv_open.status, param->srv_open.handle,
bda2str(param->srv_open.rem_bda, bda_str, sizeof(bda_str)));
spp_conn_handle = param->srv_open.handle;
spp_congested = false;
spp_connected = true;
gettimeofday(&time_old, NULL);
break;
case ESP_SPP_SRV_STOP_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_STOP_EVT");
break;
case ESP_SPP_UNINIT_EVT:
ESP_LOGI(SPP_TAG, "ESP_SPP_UNINIT_EVT");
break;
default:
break;
} }

void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) {
char bda_str[18] = {0};

switch (event) {
case ESP_BT_GAP_AUTH_CMPL_EVT:{
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(SPP_TAG, "authentication success: %s bda:[%s]", param->auth_cmpl.device_name,
bda2str(param->auth_cmpl.bda, bda_str, sizeof(bda_str)));
} else {
ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
}
break;
}
case ESP_BT_GAP_PIN_REQ_EVT:{
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
if (param->pin_req.min_16_digit) {
ESP_LOGI(SPP_TAG, "Input pin code: 0000 0000 0000 0000");
esp_bt_pin_code_t pin_code = {0};
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
} else {
ESP_LOGI(SPP_TAG, "Input pin code: 1234");
esp_bt_pin_code_t pin_code;
pin_code[0] = '1';
pin_code[1] = '2';
pin_code[2] = '3';
pin_code[3] = '4';
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
}
break;
}
#if (CONFIG_EXAMPLE_SSP_ENABLED == true)
case ESP_BT_GAP_CFM_REQ_EVT:
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %06"PRIu32,
param->cfm_req.num_val);
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
break;
case ESP_BT_GAP_KEY_NOTIF_EVT:
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%06"PRIu32, param->key_notif.passkey);
break;
case ESP_BT_GAP_KEY_REQ_EVT:
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
break;
#endif
case ESP_BT_GAP_MODE_CHG_EVT:
ESP_LOGI(SPP_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode:%d bda:[%s]", param->mode_chg.mode,
bda2str(param->mode_chg.bda, bda_str, sizeof(bda_str)));
break;
default: {
ESP_LOGI(SPP_TAG, "event: %d", event);
break;
}
}
return; }

static void uart_bridge_init(void) {
const uart_config_t uart_cfg = {
.baud_rate = UART_BRIDGE_BAUD_RATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};

ESP_ERROR_CHECK(uart_driver_install(UART_BRIDGE_PORT, UART_BRIDGE_RX_BUF_SIZE,
UART_BRIDGE_TX_BUF_SIZE, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(UART_BRIDGE_PORT, &uart_cfg));
ESP_ERROR_CHECK(uart_set_pin(UART_BRIDGE_PORT, UART_BRIDGE_TX_GPIO, UART_BRIDGE_RX_GPIO,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_LOGI(SPP_TAG, "UART bridge ready: UART2 TX=GPIO%d RX=GPIO%d %d 8N1, no flow control",
UART_BRIDGE_TX_GPIO, UART_BRIDGE_RX_GPIO, UART_BRIDGE_BAUD_RATE); }

/* TTL UART -> Bluetooth SPP */
static void uart_to_spp_task(void *arg) {
uint8_t buf[UART_BRIDGE_CHUNK];
for (;;) {
int len = uart_read_bytes(UART_BRIDGE_PORT, buf, sizeof(buf), pdMS_TO_TICKS(100));
if (len < = 0) {
continue;
}
if (!spp_connected || spp_conn_handle == 0) {
continue;
}
while (spp_congested && spp_connected) {
vTaskDelay(pdMS_TO_TICKS(10));
}
if (spp_connected && spp_conn_handle != 0) {
esp_spp_write(spp_conn_handle, len, buf);
}
} }

/* Bluetooth SPP -> TTL UART */
static void spp_to_uart_task(void *arg) {
uart_bridge_item_t item;
for (;;) {
if (xQueueReceive(spp_to_uart_queue, &item, portMAX_DELAY) == pdTRUE) {
uart_write_bytes(UART_BRIDGE_PORT, item.data, item.len);
}
} }

void app_main(void) {
char bda_str[18] = {0};
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );

uart_bridge_init();
spp_to_uart_queue = xQueueCreate(UART_BRIDGE_QUEUE_LEN, sizeof(uart_bridge_item_t));
if (spp_to_uart_queue == NULL) {
ESP_LOGE(SPP_TAG, "Failed to create UART TX queue");
return;
}
xTaskCreate(uart_to_spp_task, "uart_to_spp", 4096, NULL, 10, NULL);
xTaskCreate(spp_to_uart_task, "spp_to_uart", 4096, NULL, 10, NULL);

ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
ESP_LOGE(SPP_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}

if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
ESP_LOGE(SPP_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}

esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
#if (CONFIG_EXAMPLE_SSP_ENABLED == false)
bluedroid_cfg.ssp_en = false;
#endif
if ((ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg)) != ESP_OK) {
ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed: %s", __func__, esp_err_to_name(ret));
return;
}

if ((ret = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(SPP_TAG, "%s enable bluedroid failed: %s", __func__, esp_err_to_name(ret));
return;
}

if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) {
ESP_LOGE(SPP_TAG, "%s gap register failed: %s", __func__, esp_err_to_name(ret));
return;
}

if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) {
ESP_LOGE(SPP_TAG, "%s spp register failed: %s", __func__, esp_err_to_name(ret));
return;
}

esp_spp_cfg_t bt_spp_cfg = {
.mode = esp_spp_mode,
.enable_l2cap_ertm = esp_spp_enable_l2cap_ertm,
.tx_buffer_size = 0, /* Используется только для режима ESP_SPP_MODE_VFS */
};
if ((ret = esp_spp_enhanced_init(&bt_spp_cfg)) != ESP_OK) {
ESP_LOGE(SPP_TAG, "%s spp init failed: %s", __func__, esp_err_to_name(ret));
return;
}

#if (CONFIG_EXAMPLE_SSP_ENABLED == true)
/* Установка параметров по умолчанию для Secure Simple Pairing */
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
#endif

/*
* Установка параметров по умолчанию для Legacy Pairing
* Используется изменяемый pin, ввод pin-кода при pairing
*/
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
esp_bt_pin_code_t pin_code;
esp_bt_gap_set_pin(pin_type, 0, pin_code);

ESP_LOGI(SPP_TAG, "Own address:[%s]", bda2str((uint8_t *)esp_bt_dev_get_address(),
bda_str, sizeof(bda_str))); }

[Ссылки]

1. Bluetooth® SPP API site:espressif.com.
2. Bluetooth модуль SPP-C на базе BK3221 site:ozon.ru.
3. Bluetooth SPP Manager site:bluetooth-spp-manager.ru.
4. Windows Bluetooth Pairing for Devices Using SPP site:junipersys.com.
5. Bluetooth spp pro site:bluetooth-spp-pro.ru.
6. ESP SPP API.
7. Chapter 6A: Classic Bluetooth – The Wireless Serial Port Profile (SPP) [PDF] site:infineon.com.
8. ESP32 Bluetooth Classic: SPP Serial Communication Guide site:zbotic.in.
9. Classic Bluetooth site:docs.espressif.com.
10. Arduino и модули Bluetooth HC-05/06 site:voltiq.ru.
11. HC-06 vs HC-05: The Complete Bluetooth Module Comparison Guide for Your Projects site:pcbsync.com.
12. Buying Bluetooth Modules site:martyncurrey.com.
13. Classic Bluetooth serial port SPP site:petoi.com.
14. BL602/BL604 site:en.bouffalolab.com.
15. M0S SiPEED BL616: обзор модуля.
16. Обзор модулей Bluetooth.

 

Добавить комментарий


Защитный код
Обновить

Top of Page