|
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. |