Программирование ARM Службы BLE, руководство для начинающих Fri, October 11 2024  

Поделиться

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

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

Службы BLE, руководство для начинающих Печать
Добавил(а) microsin   

В этом руководстве будет на примере показано создание устройства Bluetooth Low Energy (BLE) с использованием сервиса BLE (перевод, оригинал см. в [1]).

Примечание: новые термины и сокращения см. в Словарике [8].

[Оборудование и ПО]

Чтобы выполнить это руководство, понадобится следующее:

• nRF52 DK Development Kit [9].
• Среда разработки (IDE) SEGGER Embedded Studio или Keil V5.xx.
• Установленное приложение nRF Connect.
• nrfjprog.
• SDK V15.0.0 [10] **ВНИМАНИЕ! Это руководство написано для SDK V15.0.0.**.
• Для nRF52 DK используется SoftDevice V6.0.0 (S132).
• Файлы примеров можно скачать на github.

Могут быть использованы другие киты, отладочные платы и версии ПО, однако в этом списке приведено то, что использовалось автором [1].

Примечание: если испытываете трудности с самостоятельной закачкой перечисленных материалов, то воспользуйтесь ссылкой [11].

Это руководство создано как естественное продолжение руководства [3]. Поэтому рекомендуется, хотя и не обязательно, предварительно изучить это руководство. Необходимы также базовые знания по использованию Keil, nRF Connect и nrfjprog, как загружать приложение в память микроконтроллера Вашей платы разработчика. Ознакомьтесь с руководством [4], чтобы научиться пользованию оборудованием и скомпилировать Ваше первое приложение BLE.

Если столкнулись с проблемами, то посетите форум Nordic DevZone (devzone site:nordicsemi.com), просмотрите документацию в Infocenter, и ознакомьтесь с документацией на Ваш кит разработчика.

[Базовая теория]

Generic Attribute Profile (GATT). Bluetooth Core Specification определяет GATT следующим образом: "Профиль GATT определяет структуру, по которой осуществляется обмен данными профиля устройства BLE. Эта структура определяет такие базовые элементы, как службы (services) и характеристики (characteristics), используемые в профиле".

Другими словами, это набор правил, описывающих как объединять, представлять и передавать данные, используя BLE. См. [5], для дополнительной информации прочитайте Bluetooth Core Specification v4.2, Vol. 3, Part G. Возможно, это покажется непростым чтением, но в конце концов должно окупиться.

Services. Стандарт Bluetooth (Bluetooth Core Specification) определяет понятие службы (или сервиса) следующим образом: "Служба (service) это набор данных и связанных с ними алгоритмов поведения, предназначенных для реализации некоторой функции или свойства устройства. [...] Определение службы может содержать в себе […] обязательные характеристики и не обязательные (опциональные) характеристики".

Другими словами, служба это коллекция информации, наподобие значений параметров сенсоров. Организация Bluetooth Special Interest Group (Bluetooth SIG) создала предопределенный список служб [2]. Например, была определена служба с именем Heart Rate Service (HRS) для мониторинга частоты сердцебиений. Причина, почему так было сделано - облегчить разработчикам создание приложений и firmware для устройств, совместимых со стандартной Heart Rate Service. Однако это не означает, что Вы не могли бы сделать свой сенсор HRS на основе своих собственный идей и структур сервиса. Иногда люди ошибочно полагают, что поскольку Bluetooth SIG определила некоторые службы, то приложения можно делать только таким образом, чтобы они соответствовали этим определениям. Это не тот случай. Можно без проблем создавать свои собственные службы для своих собственных приложений.

Characteristics. Bluetooth Core Specification определяет характеристики так: "Характеристика (characteristic) это значение, используемое службой вместе со свойствами и информацией о конфигурации - как получить доступ к значению, и как это значение должно быть отображено или представлено".

Другими словами, характеристика это место для некоторого значения параметра плюс информация о его представлении. Также в характеристике инкапсулируются параметры безопасности (security), единицы измерения (units) и другие метаданные, касающиеся характеристики.

Аналогией может служить склад, заставленный шкафами для хранения данных, где каждый шкаф имеет ряд выдвижных ящиков. Профиль GATT это как бы помещение склада. Шкафы это службы, а выдвижные ящики это характеристики, где хранится разная информация. У некоторых из этих ящиков могут быть замки, ограничивающие доступ к их информации.

Представим для примера монитор сердцебиения (Heart Rate Monitor, HRM). Наручные часы с такой функцией обычно будут использовать как минимум 2 службы:

1. Heart rate service. В этой службе инкапсулируются 3 характеристики:

a. Обязательная характеристика Heart Rate Measurement, где будет храниться частота пульса.
b. Опциональная характеристика Body Sensor Location, показывающая место положение тела человека.
c. Условная характеристика Heart Rate Control Point, точка контроля частоты пульса.

2. Battery service. В этой службе инкапсулируется одна характеристика:

a. Обязательная характеристика Battery Level, уровень заряда батареи.

Почему нужно беспокоиться об этом? Почему бы просто не посылать любые данные напрямую, без лишних действий по встраиванию данных в какие-то сервисы и характеристики? Причина тут простая - стандартизация. GATT, сервис, характеристика - все это предназначено для обеспечения совместимости и упрощения реализации. Когда мобильные устройства iPhones, Android или планшеты на Windows прослушают оповещение (advertising) BLE-устройства, где будут стандартным образом предоставлены данные службы сердцебиения, можно быть на 100% уверенным, что в этой службе будет как минимум характеристика heart rate measurement. Она там будет обязательно, потому что это гарантируется стандартизированным представлением службы. Если устройство содержит больше одной службы, то Вы свободны выбрать и использовать ту службу, которая необходима. Упакованная таким способом информация позволяет быстро обнаруживать в BLE-устройствах доступную информацию, передавать только то, что необходимо, и тем самым экономить драгоценное время и энергию. Помните, стандарт BLE был как раз разработан с целью максимально экономить потребляемую от батареи энергию.

Если продолжить аналогию: склад находится в маленьком офисе, где есть 2 заполненных комнаты. В первая комната используется бухгалтерами. Выдвижные ящики в шкафах содержат финансовую информацию по деловой активности, рассортированную по датам. Ящики закрыты на замок, открыть который могут только лишь бухгалтеры и высшее руководство. Вторая комната отведена под отдел кадров, и шкафы в ней содержат сведения по сотрудникам, рассортированные в алфавитном порядке. Здесь выдвижные ящики также снабжены замками, открывать которые могут только кадровики и высшее руководство. Каждый сотрудник в компании знает о местонахождении этих комнат, и что в них находится, но только некоторые сотрудники имеют право на доступ к находящейся под замком информации. Это гарантирует эффективность, безопасность и организованность бизнеса.

Universally Unique ID (UUID). С аббревиатурой UUID Вы будете часто встречаться в мире BLE. Это уникальный номер, используемый для идентификации служб, характеристик и дескрипторов, также известных как атрибуты. Эти ID передаются по радио, благодаря чему периферийное устройство информирует центральное устройство о предоставляемых службах. Для экономии времени передачи и занимаемой памяти в nRF52 существует 2 вида UUID: 16-битный и 128-битный.

16-bit UUID. Это короткий тип идентификатора. Предопределенной службе Heart Rate Service, например, присвоен UUID 0x180D, и одной из из её внутренних характеристик Heart Rate Measurement присвоит UUID 0x2A37. 16-разрядные UUID эффективным по занимаемой памяти и экономии энергии, однако они предоставляют относительно узкий диапазон уникальных значений; Вы можете передать по радио только лишь предопределенные заранее идентификаторы Bluetooth SIG UUID. Из-за этого появилась необходимость во втором типе UUID, благодаря которому можно будет также передавать свои собственные UUID-ы.

128-bit UUID. Это длинный UUID, который иногда называют уникальным идентификатором, специфичным для производителя (vendor specific UUID). Этот тип UUID нужно использовать, когда Вы хотите создать свои собственные службы и характеристики. 128-битный UUID может выглядеть примерно так: 4A98xxxx-1CC4-E7C1-C757-F1267DD021E8, и он называется базовым UUID (base UUID). Здесь четыре буквы x представляют поле, куда можно вставить свой собственный 16-битный ID для своих пользовательских служб и характеристик, и использовать их так же, как и предопределенные UUID. Таким способом можно сохранить базовый UUID в памяти только один раз, забыть о нем, и работать только со своими 16-битными ID, как с обычными. Вы  можете генерировать base UUID-ы с помощью утилиты nRFgo Studio.

Интересный факт касательно UUID: не существует базы данных, гарантирующей отсутствие совпадений UUID, однако если Вы сгенерируете два случайных 128-разрядных UUID, то вероятность примерно ~3e-39, что они будут одинаковыми (это примерно 1 шанс из 340,000,000,000,000,000,000,000,000,000,000,000,000).

[Пример]

Загрузите код примера с GitHub (также см. папку github архива [11]). Этот проект основан на шаблоне примера, который есть в SDK, но для упрощения из него вырезан весь код, который не является строго необходимым для наших целей. Чтобы скомпилировать проект, скопируйте его загруженные файлы в папку "nrf5x-ble-tutorial-service", находящуюся в каталоге "где_находится_ваш_SDK\examples\ble_peripheral". Если встретились с проблемами при компиляции, ищите подсказку на форуме Nordic devzone.

Если используете IDE Keil, откройте файл проекта "nrf52-ble-tutorial-service.uvprojx", который находится в папке arm5_no_packs. Если используете IDE SEGGER, то откройте файл "nrf52-ble-tutorial.services.emProject", который находится в папке ses. Автор внедрил SEGGER Real-Time Terminal (RTT) в проект, чтобы можно было легко понять, что происходит в программе. Подробнее про отладку с помощью текстовых сообщений терминала см. руководство [6].

Пример должен скомпилироваться без каких-либо ошибок, однако возможно появление некоторых предупреждений по поводу не используемых переменных. Выберите build (скомпилировать), и затем загрузите скомпилированный код в свою плату. В программе nRF Connect кликните Bluetooth Low Energy -> Open -> Start scan. Ваше устройство должно выглядеть примерно так:

BLE tutorial Services fig01

Если кликнуть Connect, то увидите следующее:

BLE tutorial Services fig02

Как можно увидеть, мы пока что ничего не делали, но уже существует 2 обязательных сервиса, предварительно настроенных для нас:

Generic Access service. У этой службы UUID 0x1800, и три обязательных характеристики:

1. Характеристика UUID 0x2A00: Device name (имя устройства).
2. Характеристика UUID 0x2A01: Appearance (внешний вид).
3. Характеристика UUID 0x2A04: Peripheral Preferred Connection Parameters (предпочтительные параметры подключения к устройству).

Generic Attribute service. У этой службы UUID 0x1801, и одна опциональная (не обязательная) характеристика:

1. Характеристика UUID 0x2A05: Service Changed (служба изменена).

Служба Generic Access Service содержит общую информацию об устройстве BLE. Вы можете распознать характеристику с именем устройства “OurService”. Вторая характеристика хранит значение внешнего вида (appearance), и в нашем случае мы в это значение ничего не установили, поэтому значение показывается просто как 0x0000. Третья характеристика хранит различные параметры, используемые для установки соединения. Вы можете найти эти значения в определениях #defines проекта, они называются MIN_CONN_INTERVAL, MAX_CONN_INTERVAL, SLAVE_LATENCY и CONN_SUP_TIMEOUT. Во врезке ниже дано короткое объяснение этих параметров.

Для связи BLE параметры соединения управляют частотой, с которой может осуществляться обмен данными между периферийным и центральным устройствами после установления связи между ними. Этими параметры согласовываются между периферийным и центральным устройствами в процессе установки связи. Параметры установки соединения следующие:

Интервал соединения (Connection Interval, CI). Центральное устройство устанавливает параметр CI, когда впервые устанавливается соединение между центральным и периферийным устройством. Периферийное устройство задает минимальное и максимальное значения, которые служат желательными пределами интервала соединения для периферийного устройства. Большинство центральных устройств будут использовать CI по умолчанию, и обычно будут игнорировать максимальные и минимальные значения, заданные периферийным устройством. Периферийному устройству обычно нужно генерировать запрос обновления параметров соединения (Connection Parameter Update Request) через некоторое время после установки соединения BLE, чтобы попытаться поменять CI для соответствия необходимому диапазону. Центральное устройство может как принять, так и отклонить этот запрос, и ответить интервалом соединения, который может как попадать, так и не попадать в указанный периферийным устройством диапазон. Если периферийное устройство примет это значение, то будет использоваться новый CI. Интервал соединения должен быть в диапазоне между 7.5 мс и 4000 мс.

Slave Latency (SL). Латентность (задержка) реакции периферийного устройства. Путем установки ненулевого значения SL периферийное устройство может несколько раз не отвечать на запрос данных со стороны центрального устройства. Однако если у периферийного устройства есть данные для отправки, то оно в любой момент может принять решение отправить данные. Это позволяет периферийному устройству спать в течение долгого времени, если у него нет данных для отправки, но в то же время быстро посылать данные при необходимости. Примерами такого поведения могут служить клавиатура и мышь, которым нужно спать как можно дольше, когда нет данных для отправки, но и сохранять при этом низкую латентность (и для мыши: малый интервал соединения CI), когда необходимо.

Connection supervisory timeout (CST). Это время таймаута контроля соединения - интервал времени от последнего обмена данными, после истечению которого связь считается потерянной. Центральное устройство не будет пытаться восстановить соединение (reconnect), пока не истечет этот таймаут. Поэтому если у Вас такое периферийное устройство, которое часто перемещается, выходя время от времени за пределы расстояния для радиосвязи, то имеет смысл использовать более короткий CST.

CI и SL обычно оказывают наибольшее влияние на производительность соединения BLE. Чем меньше SL и CI, тем более эффективно в реальном времени происходит обмен данными между периферийным устройством и центральным устройством. Обратная сторона медали при повышении быстродействия - увеличивается среднее потребление энергии периферийным устройством.

В зависимости от используемой для разработки платформы могут быть специфические рекомендации или требования по значениям этих параметров. Для iOS, компания Apple предоставляет документ "Accessory Design Guidelines for Apple Devices" (https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf), в котором, помимо всего прочего, есть правила по этим параметрам.

Вторая служба это Generic Attribute Service. Простыми словами - служба может использоваться для оповещения центрального устройства об изменениях в фундаментальной структуре сервисов и характеристик периферийного устройства. Во врезке ниже дано краткое пояснение.

Просто так базу данных GATT на лету менять нельзя.

По факту, если Вы поменяли базу данных, то должны для противоположного участника соединения разрешить и показать изменение характеристики службы. Причина в том, что противоположной стороне разрешено кешировать структуру базы данных, и любые изменения в этой структуре требуют повторного (частичного) считывания структуры базы данных (rediscovery). Если не отправить оповещение Service Changed, то это приведет к нарушению спецификации. Также изменение в базе данных обычно должно использоваться только тогда, когда Вы хотите повторно инициализировать устройство с новым профилем.

[Наш первый сервис BLE]

В примере автор добавил два файла: our_service.h и our_service.c. В них декларированы некоторые пустые функции и маленькая структура, с которых нам было бы проще всего начать работу. Автор также использовал nRFgo Studio для создания 128-битного base UUID, определенного как BLE_UUID_OUR_BASE_UUID. Поищите шаги (STEP 1-6) в файлах проекта, и Вы найдете те места, куда нужно добавить свой код.

Примечание: Nordic считает приложение nRFgo Studio устаревшим, его рекомендуется использовать только для nRF8001 и nRF24. Для nRF52 рекомендуется использовать nRFConnect.

Шаг 1: декларация структуры сервиса. Прежде всего нам нужно место для хранения всех данных и информации, относящихся к нашему сервису (службе), и для этого будет использоваться структура ble_os_t. Как Вы можете видеть, эта структура в файле our_service.h сейчас содержит только один элемент. Значение service_handle это число, идентифицирующее эту отдельную службу, и оно назначается кодом библиотек SoftDevice. Декларированием переменной m_our_service типа ble_os_t в модуле main.c можно будет передавать эту структуру в различные функции, получая полный контроль над нашей службой.

Шаг 2: инициализация сервиса. В модуле main.c уже есть функция services_init(), из которой мы будем вызывать нашу функцию our_service_init(). В качестве параметра она берет указатель на структуру ble_os_t, так что убедитесь, что он указывает на нашу переменную m_our_service:

our_service_init (&m_our_service);

Шаг 3: добавление идентификаторов UUIDs в таблицу стека BLE. Найдите определение our_service_init() в модуле our_service.c. Как можно увидеть, там почти нет кода, и сейчас нам предстоит кое-что туда добавить. Сначала нужно создать UUID для нашей службы. Поскольку мы делаем кустарную (пользовательскую) службу, то будем использовать определенный 128-битный base UUID вместе с 16-битным UUID. Введите следующий код:

uint32_t      err_code;
ble_uuid_t    service_uuid;
ble_uuid128_t base_uuid = BLE_UUID_OUR_BASE_UUID;
service_uuid.uuid = BLE_UUID_OUR_SERVICE;
err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
APP_ERROR_CHECK(err_code);

Этот код создает 2 переменные. Одна хранит 16-битный UUID службы, и другая хранит base UUID. Пятая стока добавляет наш специфичный для производителя UUID (vendor specific, об этом говорит сокращение 'vs') к таблице идентификаторов UUID в стеке BLE. Краткое описание того, что делает эта функция, можно найти во врезке ниже, также см. справочную систему nRF52 SDK. Шестая строка делает быструю проверку на случай возникновения ошибки. Хорошая практика по такому принципу оформлять код для всех вызовов API-функций, которые возвращают некоторый код ошибки, что позволяет формализовать отладку программы.

uint32_t sd_ble_uuid_vs_add (ble_uuid128_t const * p_vs_uuid, uint8_t * p_uuid_type);

Использование vendor specific UUID в основном заключается в выполнении двух шагов:

1. Добавьте ваш собственный базовый 128-битный идентификатор (base UUID) в стек с помощью функции sd_ble_uuid_vs_add(). Сохраните возвращенное этой функцией значение в параметре p_type вызова этой функции.

2. Установите тип всех переменных ble_uuid_t, которые должны использовать это базовое значение, возвращенное из sd_ble_uuid_vs_add(). Когда Вы установите это поле в свой собственный тип вместо BLE_UUID_TYPE_BLE, значение будет использоваться поверх custom base UUID, заданного Вами, вместо базового Bluetooth SIG.

Своим вызовом функция sd_ble_uuid_vs_add() добавит Ваш base UUID в внутреннему списку базовых UUID кода SoftDevice, и вернет индекс таблицы для этого UUID в поле p_uuid_type. Когда этот тип позже будет использоваться в ble_uuid_t later, код SoftDevice сможет найти этот базу в той же самой таблице, используя этот индекс.

Ниже приведен небольшой кусок псевдокода, показывающий основные моменты этой схемы.

#define BLE_UUID_NUS_SERVICE           0x0001
#define BLE_UUID_NUS_TX_CHARACTERISTIC 0x0002
#define BLE_UUID_NUS_RX_CHARACTERISTIC 0x0003
 
typedef struct ble_nus_s
{
  uint8_t uuid_type;   /**< Тип UUID для Nordic UART Service Base UUID. */
  ...
} ble_nus_t;
 
...
 
service_init:
  uint32_t   err_code;
  ble_uuid_t ble_uuid;
  ble_uuid128_t nus_base_uuid = {0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
                                 0x93, 0xF3, 0xA3, 0xB5, 0x00, 0x00, 0x40, 0x6E};
 
  // Добавление кустарного base UUID:
  err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
  if (err_code != NRF_SUCCESS)
  {
    return err_code;
  }
 
  // Добавление службы:
  ble_uuid.type = p_nus->uuid_type;
  ble_uuid.uuid = BLE_UUID_NUS_SERVICE;
 
  err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                      &ble_uuid,
                                      &p_nus->service_handle);
  if (err_code != NRF_SUCCESS)
  {
    return err_code;
  }
 
  ...
 
char_add:
  ble_uuid.type = p_nus->uuid_type;
  ble_uuid.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC;
  ...
  return sd_ble_gatts_characteristic_add(p_nus->service_handle, &char_md,
                                          &attr_char_value,
                                          &p_nus->rx_handles);

Шаг 4: добавление службы. Теперь все готово к инициализации нашего сервиса. Сразу после кода, введенного на шаге 3, добавьте следующее:

err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                    &service_uuid,
                                    &p_our_service->service_handle);
APP_ERROR_CHECK(err_code);

Функция sd_ble_gatts_service_add() принимает 3 параметра. В первом мы указываем, что хотим получить первичную (primary) службу. Как альтернативная опция здесь может быть указано BLE_GATTS_SRVC_TYPE_SECONDARY, что создаст вторичную (secondary) службу. Вторичная служба используется редко, однако иногда применяют вложение служб друг в друга (вторичная служба логически находится внутри первичной). Второй параметр это указатель на UUID службы, который мы создали. Путем передачи этой переменной в sd_ble_gatts_service_add() наша служба может быть уникально идентифицирована стеком BLE. Третья переменная, переданная в функцию это указатель на то место, куда должен быть сохранен числовой дескриптор service_handle этой уникальной службы. Функция sd_ble_gatts_service_add() создаст таблицу, содержащую наши службы, и service_handle это просто индекс, указывающий на нашу определенную службу в этой таблице.

Функция our_service_init() должна теперь выглядеть примерно так:

void our_service_init(ble_os_t * p_our_service)
{
  uint32_t err_code;  // Переменная, куда будут попадать коды ошибки
                      // из библиотечных функций и функций SoftDevice.
  
  // Декларация 16-битного UUID для службы и 128-битного
  // UUID, и добавление их в стек BLE:
  ble_uuid_t    service_uuid;
  ble_uuid128_t base_uuid = BLE_UUID_OUR_BASE_UUID;
  service_uuid.uuid = BLE_UUID_OUR_SERVICE;
  err_code = sd_ble_uuid_vs_add(&base_uuid, &service_uuid.type);
  APP_ERROR_CHECK(err_code);    
  
  // Добавление службы:
  err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
                                      &service_uuid,
                                      &p_our_service->service_handle);
  APP_ERROR_CHECK(err_code);
 
  SEGGER_RTT_WriteString(0, "Executing our_service_init().\n");
  SEGGER_RTT_printf(0, "Service UUID: 0x%#04x\n", service_uuid.uuid);
  SEGGER_RTT_printf(0, "Service UUID type: 0x%#02x\n", service_uuid.type);
  SEGGER_RTT_printf(0, "Service handle: 0x%#04x\n", p_our_service->service_handle);
}

Скомпилируйте проект и загрузите программу в память чипа, и снова откройте утилиту nRF Connect. Запустите другое сканирование, и кликните на Connect. Сейчас Вы должны увидеть нашу службу с кустарным UUID внизу. Можно распознать этот base UUID из определения #define в заголовке our_service.h, и если посмотреть внимательнее, то можно также увидеть и наш 16-битный UUID службы: 0000 ABCD -1212-EFDE-1523-785FEF13D123.

BLE tutorial Services fig03

Если открыт Segger RTT, то также будет видна некоторая информация о ходе выполнения программы, которую выводят строки SEGGER_RTT. Если эти строки раскомментированы, то при запуске приложения Вы сможете увидеть, какие переменные используются в службе. Например, дескриптор службы (service handle) установлен в 0x000E. Если поместить курсор на нашу службу, то будет видно и это значение дескриптора:

BLE tutorial Services fig04

Когда Вы подключаетесь к своему устройству с помощью nRF Connect, то также увидите, какие события происходят в стеке BLE. Эти события "захватываются" в функции ble_event_handler() из модуля main.c. Первое событие - событие BLE_GAP_EVT_CONNECTED из Generic Access Profile (GAP), показывающее факт установки соединения и значение 0x00 дескриптора этого соединения. Если Вы когда-нибудь создадите приложение с несколькими подключениями, то получите несколько дескрипторов соединений, каждому из этих соединений будет присвоено уникальное значение дескриптора. Через несколько секунд Вы получите событие BLE_GAP_EVT_CONN_PARAM_UPDATE,  показывающее, что MCP и Ваше устройство договорились о параметрах соединения. Параметры соединения можно увидеть в определениях #defines нашего примера, это MIN_CONN_INTERVAL, SLAVE_LATENCY и CONN_SUP_TIMEOUT. Во врезке ниже дано краткое объяснение процесса договора о параметрах соединения. В завершение кликните в MCP, и Вы получите событие BLE_GAP_EVT_DISCONNECTED, сообщающее об отключении и его причине.

BLE tutorial Services fig05

Изначально главное устройство (Central) будет соединяться с периферийным устройством (Peripheral), по своему усмотрению назначая параметры соединения. После того, как соединение установлено, Peripheral может отправить для Central запрос на обновление параметров соединения (connection parameter update request), и скорее всего Central ответит обновлением параметров соединения. Однако по спецификации стандарта Bluetooth устройство Central не обязан отвечать на запрос обновления параметров соединения, и может просто его игнорировать. Принятое обновление от Central не обязательно будет тем, что запрашивало периферийное устройство. Если периферийное устройство не может принять обновление параметра от Central, то оно может выбрать отключение (разрыв соединения с Central).

Во время процесса согласования параметров соединения к Central посылаются следующие параметры, как пример:

// Минимально допустимый интервал соединения (0.5 секунды):
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(500, UNIT_1_25_MS)
// Максимально допустимый интервал соединения (1 секунда):
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(1000, UNIT_1_25_MS)
// Латентность подчиненного устройства:
#define SLAVE_LATENCY     0
// Таймаут контроля соединения (4 секунды):
#define CONN_SUP_TIMEOUT  MSEC_TO_UNITS(4000, UNIT_10_MS)

Для интервала соединения установлен желаемый минимум и максимум, так что для устройства Central доступна некоторая гибкость в выборе интервала.

Если периферийное устройство получает обновление параметров соединения от Central, которое неприемлемо, то оно может отправить другой запрос на обновление параметров соединения к Central, чтобы заново договориться о параметрах, которые могли бы быть допустимы. Количество отправляемых запросов на обновление параметров соединения определяется константой MAX_CONN_PARAMS_UPDATE_COUNT, как это сделано в примере ble_app_hrs из SDK. Макрос FIRST_CONN_PARAMS_UPDATE_DELAY определяет, сколько времени должно пройти от начала оповещения до отправки первого запроса обновления параметров соединения, и NEXT_CONN_PARAMS_UPDATE_DELAY определяет, через какое время должны обновляться последующие запросы на обновление параметров соединения.

В примере ble_app_hrs запрос обновления параметров инициируется запуском таймера в приложении с идентификатором m_sensor_contact_timer_id. Таймер инициализируется вызовом conn_params_init() в функции main, и скорее всего запускается в обработчике события соединения.

Advertising. В предыдущем руководстве [3] мы обсуждали разные аспекты пакета оповещения (advertising), и сейчас время для оповещения с нашим base UUID. Поскольку у base UUID длина 16 байт, а advertising-пакет уже содержит некоторые данные, в самом этом пакете не будет достаточно места. Поэтому вместо этого нам нужно поместить его в пакет ответа сканирования (scan response).

Шаг 5: декларация переменной, где хранится UUID нашей службы. Перед функцией advertising_init() в модуле main.c декларируйте переменную, хранящую идентификатор нашей службы:

static ble_uuid_t m_adv_uuids[] = 
{
  {
    BLE_UUID_OUR_SERVICE,
    BLE_UUID_TYPE_VENDOR_BEGIN
  }
};

BLE_UUID_OUR_SERVICE, как Вы уже знаете, это UUID нашей службы, и BLE_UUID_TYPE_VENDOR_BEGIN показывает, что он является частью vendor specific base UUID. Если более конкретно, то BLE_UUID_TYPE_VENDOR_BEGIN это индекс, указывающий на наш base UUID в таблице идентификаторов UUID, которую мы инициировали в our_service_init().

Шаг 6: декларация и создание экземпляра scan response. Добавьте UUID к пакету scan response следующим образом:

init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids  = m_adv_uuids;

Теперь процедура advertising_init() должна выглядеть примерно так:

static void advertising_init(void)
{
  ret_code_t             err_code;
  ble_advertising_init_t init;
 
  memset(&init, 0, sizeof(init));
 
  init.advdata.name_type              = BLE_ADVDATA_FULL_NAME;
  init.advdata.flags                  = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
  
  init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
  init.srdata.uuids_complete.p_uuids  = m_adv_uuids;
 
  init.config.ble_adv_fast_enabled  = true;
  init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
  init.config.ble_adv_fast_timeout  = APP_ADV_DURATION;
 
  init.evt_handler = on_adv_evt;
 
  err_code = ble_advertising_init(&m_advertising, &init);
  APP_ERROR_CHECK(err_code);
 
  ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}

Снова скомпилируйте и загрузите программу в память чипа. Теперь наше устройство должно отображаться в списке MCP распознанных устройств вот так:

BLE tutorial Services fig06

Теперь Вы знаете, как настроить и создать свою первую базовую службу в устройстве BLE. Если нужно добавить в устройство больше служб, то можно просто реплицировать функцию our_service_init(), и таким образом определить больше идентификаторов UUID для служб.

Однако это только половина пути. Предстоит еще разобрать характеристики, которые должны хранить Ваши данные. Эта тема рассматривается в руководстве [7].

[Ссылки]

1. Bluetooth low energy Services, a beginner's tutorial site:nordicsemi.com.
2. predefined certain services site:bluetooth.com.
3. Оповещения BLE, руководство для начинающих.
4. Getting started with nRF5 SDK and SES (nRF51 & nRF52 Series) site:nordicsemi.com.
5. Bluetooth Low Energy GATT.
6. Debugging with Real Time Terminal site:nordicsemi.com.
7. Bluetooth low energy Characteristics, a beginner's tutorial site:nordicsemi.com.
8Bluetooth: аббревиатуры и термины.
9. nRF52 DK site:nordicsemi.com.
10nRF5_SDK.
11. 210713BLE-Services-tutorial.zip - пример приложения, SDK, утилиты.

 

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


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

Top of Page