№10 / 2017 / статья 2

BLE-маячок на базе СС2650: просто и по шагам

Александр Калачев, Максим Лапин (г. Барнаул)

Реализация устройств с автономным питанием для Интернета вещей, передающих общедоступные данные по технологии BLE на планшеты и смартфоны пользователей, является не столь сложной инженерной задачей, если воспользоваться разработками Texas Instruments: платформой для прототипирования SensorTag и модулем CC2650MODA, – а также программным пакетом Evothings Studio совместно с Cordova BLE plugin.

Одними из массовых устройств в среде Интернета вещей (IoT) могут стать так называемые маячки (beacon) – небольшие устройства с автономным питанием, периодически передающие в эфир небольшие блоки данных. Данные эти, в зависимости от назначения маячка, могут быть или статическими, или динамическими – передающими сведения о параметрах окружающей среды или состоянии объекта. Данные, передаваемые маячками, как правило, доступны любому пользовательскому устройству, находящемуся в радиусе их действия. Этот радиус, впрочем, редко превышает несколько десятков метров.

На сегодняшний день наиболее популярная технология для реализации маячков – Bluetooth Low Energy (BLE), что объясняется широким распространением технологии Bluetooth (с поддержкой BLE) в мобильных пользовательских устройствах наподобие смартфонов, планшетов, ноутбуков и прочего.

Маячки, использующие Bluetooth Low Energy, как правило, предназначены для автономной работы на одной батарее или аккумуляторе. Малое энергопотребление достигается за счет уменьшения времени передачи и перехода в спящий режим между отправкой пакетов. Информация при этом распространяется при помощи механизма объявлений (Advertisement) – широковещательных рассылок технологии BLE [1].

Подходящие режимы для BLE-маячка – это «периферийное устройство» (Peripheral) и «широковещательный передатчик». В данных режимах посылается один и тот же тип объявлений за исключением одного определенного флага внутри пакета, который указывает, является устройство соединяемым или несоединяемым [1].

Если маячок работает в режиме широковещательного передатчика – достигается наибольшая экономия энергии. Взаимодействие с маячком в данном режиме – только одностороннее: он лишь передает в эфир свои данные и не может получать данные или настройки от внешнего устройства. Это вполне логично для устройства, находящегося практически в «общем доступе» – нет практической необходимости в изменении настроек со стороны пользователей.

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

Постановка задачи

Рассмотрим одну из типовых задач – разработку маячка датчика температуры и влажности воздуха. Устройства подобного типа могут быть востребованы для мониторинга климата в торговых и складских помещениях, рефрижераторных установках, теплицах и так далее.

Широковещательные пакеты технологии BLE имеют формат, представленный на рисунке 1 [1].

Рис. 1. Формат широковещательных пакетов BLE

Рис. 1. Формат широковещательных пакетов BLE

На полезную нагрузку, которой и будут являться данные о температуре и влажности, приходится не более 31 байта, а с учетом рекомендаций по формату пакетов – не более 25 байтов. Для передачи показаний температуры и влажности места достаточно (таблица 1) – и в бинарном виде (максимум 3…4 байта на параметр), и в текстовом (по 6…8 байт на параметр).

Таблица 1. Типы данных объявления, формат данных, специфичных для изготовителя [1]

Байт Значение Описание
0 0x03…0x1F Длина этих данных
1 0xFF Флаг данных, специфичных для изготовителя
2 0x0D Идентификатор компании
3 0x00 Идентификатор компании (например, 0x000D – Texas Instruments)
4…31 Определяемые пользователем (дополнительные) данные

Фактически задача приложения, обслуживающего маячок – периодически опрашивать нужный датчик и модифицировать содержимое пакетов-объявлений (Advertisement, или adv-пакетов).

Прототипирование маячка на SensorTag

Имея под рукой отладочную плату SensorTag, можно достаточно быстро провести прототипирование BLE-маячка. Необходимый датчик на плате установлен, это HDC1000, наличие ошибок со стороны аппаратной части исключено.

В качестве основы для программного проекта использованы проекты-примеры I²C Light Sensor и ProjectZero.

I²C Light Sensor демонстрирует пример работы независимого процессорного ядра в CC2650 – так называемого 16-битного контроллера датчиков Sensor Controller (SC). В проекте I²C Light Sensor проведем модификации, запрещающие работу остальных датчиков на плате SensorTag, производящие настройку датчика температуры-влажности и осуществляющие его опрос. По завершению опроса будет сгенерирован сигнал ALERT.

В проект ProjectZero будет добавлено взаимодействие с Sensor Controller и модификация advertisiment-пакетов. Действия по изменению программных проектов будут во многом аналогичны описанным в статье [2].

Настройка опроса датчика температуры-влажности HDC1000

Всю работу с датчиком температуры-влажности будет выполнять Sensor Controller, оповещая основное ядро о завершении измерений. Для создания кода будет использована среда Sensor Controller Studio.

Код отдельной задачи SC содержит четыре секции:

  • Initialization Code, запускаемый при старте задачи;
  • Execution Code, вызываемый периодически по срабатыванию AUX-RTC-таймера (задействован канал таймера AON_RTC channel 2);
  • Event Handler Code, обрабатывающий события в таймере или на внешних выводах;
  • Termination Сode, выполняемый по завершению задачи.

В секции Initialization Code разместим код, запрещающий работу некоторых датчиков на плате SensorTag. К таковым относятся датчик освещенности OPT3001 и ИК-датчик температуры TMP007:

// переводим OPT3001 в режим ожидания
// Shut down the light sensor
   i2cStart();
   i2cTx(I2C_OP_WRITE | ALS_I2C_ADDR); // 1000101_0 ->8a
   i2cTx(ALS_REG_CFG);
   i2cTx(ALS_CFG_RESET >> 8);
   i2cTx(ALS_CFG_RESET >> 0);
   i2cStop();
// переводим TMP007 в режим ожидания
// tmp007 config to disable conversion
   i2cStart();
   i2cTx(I2C_OP_WRITE | 0x88); //1000100_0->88
   i2cTx(0x02);
   i2cTx(0x0A);
   i2cTx(0x40);
   i2cStop();
// Schedule the next execution
   fwScheduleTask(1);

В секции Execution Code оставляем только настройку задержки срабатывания по таймеру 1 и повторный запуск секции кода по срабатыванию RTC-таймера.

Устанавливается задержка срабатывания события по таймеру AUX Timer 1, SC переводится в режим Standby до срабатывания триггера таймера. Задержка задается в количестве 4 кГц тактовых импульсов, генерируемых RTC. Первый аргумент функции – всегда 0 для текущих версий (не используется). Второй аргумент – мантисса числа mant × 2^exp [4 kHz ticks] (диапазон 1…255), третий аргумент – его экспонента (диапазон 0…15). То есть, в данном случае задержка срабатывания – 120 × 23 импульсов или 120 × 8 = 960 импульсов по 0,25 мс каждый, итого – 240 мс.

После выхода из обработчика события запускаем повтор кода секции через одно срабатывание таймера AUX-RTC.

// настройка задержки срабатывания
   evhSetupTimerTrigger(0, 120, 3);
// настройка повторного запуска
// Schedule the next execution
   fwScheduleTask(1);

Основная работа с датчиком будет происходить в секции Event Handler Code.

По умолчанию HDC1000 находится в режиме одиночных измерений температуры и влажности [3]. Алгоритм запуска измерений и чтения результатов следующий:

  • обращение к датчику на запись;
  • передача адреса внутреннего регистра датчика – 0х00;
  • ожидание в течение примерно 15 мс – около 7,5 мс для завершения измерения температуры и столько же – для влажности;
  • обращение к датчику на чтение;
  • последовательное чтение четырех байтов: первые два байта будут 16-битным кодом температуры, вторые два – 16-битным кодом влажности.

Согласно электрической принципиальной схеме, SensorTag HDC1000 настроен на адрес 0х86 (7-битный двоичный адрес – 0b1000011).

// If a measurement was successfully started during the last execution ...
   if (state.i2cStatus == 0x0000) {
// HDC1000
//записываем по адресу HDC1000 (0x86) адрес регистра с которого будем читать
// – 0х00, тем самым запускается процесс измерений
   i2cStart();
   i2cTx(I2C_OP_WRITE | 0x86);
   i2cTx(0x00); //temperature register
// ждем 15 мс
   fwDelayUs(15000, FW_DELAY_RANGE_100_MS);
// If successful ...
// считываем результаты
   if (state.i2cStatus == 0x0000) {
   U16 resultRegH;
   U16 resultRegL;
// обращение по адресу 0х86 на чтение
// Read the result
   i2cRepeatedStart();
   i2cTx(I2C_OP_READ | 0x86);
   i2cRxAck(resultRegH);
   i2cRxAck(resultRegL);
// собираем показания температуры
   U16 value = (resultRegH<<8)|resultRegL; //temp
   output.temperature = value;
   i2cRxAck(resultRegH);
   i2cRxNack(resultRegL); //hum
   i2cStop();
// собираем показания влажности
   value = (resultRegH<<8)|resultRegL;
   output.humidity = value;
} else {
  i2cStop();
}
// генерируем событие ALERT для основного ядра
   fwGenAlertInterrupt();
}

Для того чтобы можно было использовать функцию fwDelayUs();, в настройках проекта необходимо выбрать опцию Delay Insertion в разделе Utilites (рисунок 2).

Рис. 2. Выбор дополнительных библиотек для проекта в Sensor Controller Studio

Рис. 2. Выбор дополнительных библиотек для проекта в Sensor Controller Studio

Также в разделе Data structures нужно будет добавить две переменные типа output – temperature и humidity.

В секции Termination Code можно прописать код для выключения датчика освещенности и ИК-датчика температуры.

// Shut down the light sensor
   i2cStart();
   i2cTx(I2C_OP_WRITE | ALS_I2C_ADDR); // 1000101_0 ->8a
   i2cTx(ALS_REG_CFG);
   i2cTx(ALS_CFG_RESET >> 8);
   i2cTx(ALS_CFG_RESET >> 0);
   i2cStop();
// tmp007 config to disable conversion
   i2cStart();
   i2cTx(I2C_OP_WRITE | 0x88); //1000100_0->88
   i2cTx(0x02);
   i2cTx(0x0A);
   i2cTx(0x40);
   i2cStop();
// Cancel the potentially active event trigger
   evhCancelTrigger(0);

Проводим тестирование кода через Task Testing (рисунок 3). В данном тесте показания датчика по температуре – 29308, по влажности – 30916. Измерения проводились в жилой комнате многоэтажного дома летом, положение платы SensorTag – возле монитора ноутбука.

Рис. 3. Тестирование работы с датчиком HDC1000

Рис. 3. Тестирование работы с датчиком HDC1000

Согласно документации на HDC1000 [3], производим пересчет показаний в градусы Цельсия и проценты относительной влажности воздуха.

Для температуры:

$$T=\frac{temperature}{2^{16}}\times 165^\circ C-40^\circ C=\frac{29308}{65536}\times 165^\circ C-40^\circ C=33.79^\circ C.$$

Для влажности:

$$H=\frac{humidity}{2^{16}}\times 100\%=\frac{30916}{65536}\times 100\%=47.17\%.$$

В целом можно видеть, что код работает.

Переходим к изменению кода ProjectZero. Основная цель – интеграция задачи, работающей с BLE, с задачей, выполняющейся на SC, и модификация adv-пакетов.

Для исключения возникновения ошибок линковщика в проекте комментируются все отладочные выводы в лог. Также в папку проекта копируются файлы, сгенерированные в Sensor Controller Studio ранее:

scif.c;
scif.h;
scif_framework.c;
scif_framework.h;
scif_osal_tirtos.c;
scif_osal_tirtos.h;
ex_include_tirtos.h.

В конец секции INCLUDES добавляются подключения библиотек для SC и определяются прототипы функций уведомления основного ядра о событиях SC.

/*********************************************************************
* INCLUDES
#include "ex_include_tirtos.h"
#include "scif.h"
#define BV(n) (1 << (n))
// Task data
   Task_Struct myTask;
   Char myTaskStack[1024];
// Semaphore used to wait for Sensor Controller task ALERT event
static Semaphore_Struct semScTaskAlert;
void scCtrlReadyCallback(void);
void scTaskAlertCallback(void);

К типам обрабатываемых событий добавляются события, генерируемые SC.

/*********************************************************************
* TYPEDEFS
*/
// Types of messages that can be sent to the user application task from other
// tasks or interrupts. Note: Messages from BLE Stack are sent differently.
typedef enum
{
  APP_MSG_SERVICE_WRITE = 0, /* A characteristic value has been written  */
  APP_MSG_SERVICE_CFG, /* A characteristic configuration has changed */
  APP_MSG_UPDATE_CHARVAL, /* Request from ourselves to update a value  */
  APP_MSG_GAP_STATE_CHANGE, /* The GAP / connection state has changed   */
  APP_MSG_BUTTON_DEBOUNCED, /* A button has been debounced with new value */
  APP_MSG_SEND_PASSCODE, /* A pass-code/PIN is requested during pairing */
  APP_MSG_SC_CTRL_READY,
  APP_MSG_SC_TASK_ALERT,
} app_msg_types_t;

В секции локальных переменных немного изменим структуру данных, которые передаются в adv-пакетах, благодаря чему изменится тип поля (раньше в нем дублировалось имя проекта) и его длина. Именно данные массива advertData[ ] и передаются в пакетах объявлений. Следовательно, изменяя их содержимое, можно будет передавать нужные данные в эфир. Фактически для периодической передачи показаний датчика температуры-влажности достаточно просто изменять 6-7 и 8-9 байты массива advertData[ ]. Например, в 6-7 байты будут помещаться данные о температуре, а в 8-9 – байты о влажности.

static uint8_t advertData[ ] =
{
// Flags; this sets the device to use limited discoverable
// mode (advertises for 30 seconds at a time) or general
// discoverable mode (advertises indefinitely), depending
// on the DEFAULT_DISCOVERY_MODE define.
   0x02, // length of this data
   GAP_ADTYPE_FLAGS, DEFAULT_DISCOVERABLE_MODE | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
   0x05, // length of this data
   GAP_ADTYPE_16BIT_MORE,
   0,
   0,
   0,
   0,
};
// GAP GATT Attributes
static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "Hum&Temp ";

Следующее «традиционное» изменение проекта ProjectZero, специфичное для платы SensorTag – настройка неиспользуемых выводов. Это нужно сделать для снижения общего потребления платы.

PIN_State unusedPinState;
PIN_Config unusedPinTable[ ] =
{
   Board_MPU_POWER | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, // MPU power off
// Board_SPI0_MOSI | PIN_INPUT_DIS | PIN_PULLDOWN, // disable input, enable pulldown to give a defined level on the bus
// Board_SPI0_CLK | PIN_INPUT_DIS | PIN_PULLDOWN, // disable input, enable pulldown to give a defined level on the bus
   PIN_TERMINATE
};
   PIN_Handle unusedPinHandle;

Основная работа в данном примере будет осуществляться в функции processTaskAlert(), которая будет вызываться каждый раз при получении основным ядром сигнала ALERT. В данной функции от SC принимаются показания температуры и влажности. Эти значения копируются в советующие места в массиве advertData[ ] и вызывается функция, актуализирующая новые данные в adv-пакетах – GAPRole_SetParameter().

Функция processTaskAlert() с callback-функциями для обработки сообщений SC определяется в начале секции PUBLIC FUNCTIONS.

/*********************************************************************
* PUBLIC FUNCTIONS
*/
/*
* @brief Task creation function for the user task.
* @param None.
* @return None.
*/
//++++++++++++++++++++++++++++++++++
static void scCtrlReadyCallback(void)
{
// Notify application `Control READY` is active
   user_enqueueRawAppMsg(APP_MSG_SC_CTRL_READY, NULL, 0);
} // scCtrlReadyCallback
static void scTaskAlertCallback(void)
{
// Notify application `Task ALERT` is active
   user_enqueueRawAppMsg(APP_MSG_SC_TASK_ALERT, NULL, 0);
} // scTaskAlertCallback
static void processTaskAlert(void)
{
// Clear the ALERT interrupt source
   scifClearAlertIntSource();
   uint16_t Temp; //
   uint16_t Hum; //
float HDCTemp;
float HDCHum;
   Temp=scifTaskData.i2cLightSensor.output.temperature;
   Hum=scifTaskData.i2cLightSensor.output.humidity;
   HDCTemp=(Temp*165.0)/65536.0 – 40.0;
   HDCHum=(Hum*1.0)/65536.0;
   advertData[5]=HI_UINT16(Temp);
   advertData[6]=LO_UINT16(Temp);
   advertData[7]=HI_UINT16(Hum);
   advertData[8]=LO_UINT16(Hum);
// Initialize Advertisement data
   GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData);
// Acknowledge the ALERT event
   scifAckAlertEvents();
} // processTaskAlert

Ниже, в функции ProjectZero_init(void) настраиваем нужным образом неиспользуемые выводы.

unusedPinHandle = PIN_open(&unusedPinState, unusedPinTable);

Перед настройкой параметров BLE инициализируем SC.

// Initialize the Sensor Controller
   scifOsalInit();
   scifOsalRegisterCtrlReadyCallback(scCtrlReadyCallback);
   scifOsalRegisterTaskAlertCallback(scTaskAlertCallback);
   scifInit(&scifDriverSetup);
// Set the Sensor Controller task tick interval to 1 second
   scifStartRtcTicksNow(0x00010000);
   scifStartTasksNbl(BV(SCIF_I2C_LIGHT_SENSOR_TASK_ID));

Финальной модификацией исходного проекта станет добавление в обработчик событий user_processApplicationMessage() возможности обработки события ALERT от SC.

static void user_processApplicationMessage(app_msg_t *pMsg)
{
   char_data_t *pCharData = (char_data_t *)pMsg->pdu;
switch (pMsg->type)
{
case APP_MSG_SERVICE_WRITE: /* Message about received value write */
/* Call different handler per service */
switch(pCharData->svcUUID) {
case LED_SERVICE_SERV_UUID:
   user_LedService_ValueChangeHandler(pCharData);
break;
case DATA_SERVICE_SERV_UUID:
   user_DataService_ValueChangeHandler(pCharData);
break;
}
break;
case APP_MSG_SERVICE_CFG: /* Message about received CCCD write */
/* Call different handler per service */
switch(pCharData->svcUUID) {
case BUTTON_SERVICE_SERV_UUID:
   user_ButtonService_CfgChangeHandler(pCharData);
break;
case DATA_SERVICE_SERV_UUID:
   user_DataService_CfgChangeHandler(pCharData);
break;
}
break;
case APP_MSG_UPDATE_CHARVAL: /* Message from ourselves to send */
   user_updateCharVal(pCharData);
break;
case APP_MSG_GAP_STATE_CHANGE: /* Message that GAP state changed */
   user_processGapStateChangeEvt( *(gaprole_States_t *)pMsg->pdu );
break;
case APP_MSG_SEND_PASSCODE: /* Message about pairing PIN request */
{
   passcode_req_t *pReq = (passcode_req_t *)pMsg->pdu;
// Log_info2("BondMgr Requested passcode. We are %s passcode %06d",
// (IArg)(pReq->uiInputs?"Sending":"Displaying"),
// DEFAULT_PASSCODE);
// Send passcode response.
   GAPBondMgr_PasscodeRsp(pReq->connHandle, SUCCESS, DEFAULT_PASSCODE);
}
break;
case APP_MSG_BUTTON_DEBOUNCED: /* Message from swi about pin change */
{
   button_state_t *pButtonState = (button_state_t *)pMsg->pdu;
   user_handleButtonPress(pButtonState);
}
break;
//+++++++++++++++++++++++++++++++
case APP_MSG_SC_TASK_ALERT:
   processTaskAlert();
break;
//+++++++++++++++++++++++++++++++
}
}

Компилируем проект, подключаем связку SensorTag + XDS1100 и прошиваем при помощи SmartRF Flash Programmer 2.

Работу приложения проверяем простой программой BLE-сканера для Android, например, BLE Scanner. Содержимое adv-пакетов можно просмотреть в «RAW DATA» (рисунок 4).

Рис. 4. Просмотр и расшифровка содержимого adv-пакетов маячка

Рис. 4. Просмотр и расшифровка содержимого adv-пакетов маячка

Подключение внешних датчиков к связке SensorTag+XDS110

Следующим этапом разработки будет тестирование подключения внешнего датчика к SensorTag. Из-за конструктивных особенностей SensorTag (специфический разъем) удобнее всего использовать связку SensorTag + XDS110 и подключать внешние устройства непосредственно к отладочной плате.

На стороне XDS110, противоположной стороне подключения SensorTag, есть контактные площадки с линиями шины I²C, линиями DP0..DP3, а также посадочное место для двухрядного штыревого разъема (шаг 1,27) с несколькими линиями, шинами I²C и SPI.

В данном примере будут задействованы контактные площадки с шиной I²C, к которым подпаивается 4-контактный штыревой разъем (рисунок 5).

Рис. 5. Контакты и места монтажа разъемов для внешних устройств XDS110

Рис. 5. Контакты и места монтажа разъемов для внешних устройств XDS110

В качестве внешнего I²C-устройства выступит датчик температуры-влажности воздуха HDC1080 [4], смонтированный на небольшой плате (рисунок 6). С программной точки зрения он не отличается от HDC1000, за исключением I²C-адреса – у HDC1080 он фиксированный и равен 0x80 (семибитная версия – 0b1000000), и, что довольно удачно, этот адрес не совпадает с адресами других датчиков, установленных на SensorTag.

Рис. 6. Связка SensorTag + XDS100 с подключенным датчиком HDC1080 на отдельной плате

Рис. 6. Связка SensorTag + XDS100 с подключенным датчиком HDC1080 на отдельной плате

Для работы с внешним HDC1080 достаточно будет всего лишь изменить адрес в коде опроса HDC1000 (в секции Event Handler) в проекте для SCS, сгенерировать новые исходные тексты для CCS и скопировать их в папку с проектом ProjectZero.

В данном фрагменте кода для SC выделено место, которое подлежит изменению:

// If a measurement was successfully started during the last execution ...
   if (state.i2cStatus == 0x0000) {
// HDC1080
//записываем по адресу HDC1000 (0x86) адрес регистра, с которого будем читать
// – 0х00, тем самым запускается процесс измерений
   i2cStart();
   i2cTx(I2C_OP_WRITE | 0x80);

Тестирование кода не отличается от предыдущего: собирается проект; образ приложения, совместно с образом стека, прошивается с SensorTag; посредством приложения на смартфоне (использовался BLE Scanner для Android) ищется маячок и просматривается содержимое adv-пакета.

Модуль CC2650MOD

Одним из интересных решений для реализации устройств на базе CC2650 является миниатюрный модуль CC2650MOD. Его размеры всего 17х11х2,7 мм, но при этом он имеет всю необходимую обвязку по линиям питания, кварцевые резонаторы (основной и «часовой»), и, что наиболее важно, обвязку антенного тракта и керамическую антенну.

Внешний вид модуля и расположение его выводов представлены на рисунке 7.

Рис. 7. Внешний вид модуля CC2650MOD и его распиновка

Рис. 7. Внешний вид модуля CC2650MOD и его распиновка

Типовая схема подключения модуля практически не требует внешних элементов (рисунок 8) – необходимо подать питание на соответствующие выводы, подтянуть вывод сброса и предусмотреть возможность программирования модуля через линии JTAG-интерфейса. Для программирования модуля достаточно всего двух сигнальных линий – TCK и TMS – и сигнала сброса.

Рис. 8. Типовая схема подключения модуля CC2650MOD

Рис. 8. Типовая схема подключения модуля CC2650MOD

Подключение модуля для прошивки к Debugger DevPack, с учетом линий питания, требует всего пяти линий (рисунок 9). Дополнительный подтягивающий резистор на линии nResrt уже есть на плате Debugger DevPack и не обязателен при работе с данным программатором, хотя этот резистор необходимо иметь в конечном устройстве.

Рис. 9. Подключение модуля CC2650MODА для прошивки к Debugger DevPack

Рис. 9. Подключение модуля CC2650MODА для прошивки к Debugger DevPack

При портировании кода на модуль CC2650MODA, особенно если предварительно этот код тестировался/отлаживался на платах TI SensorTag или CC2650 LaunchPad (LAUNCHXLCC2650), следует внести небольшие изменения в конфигурацию линий ввода-вывода и радиочасти. Связано это с тем, что в указанных отладочных средствах применяется вариант CC2650 в корпусе QFN 7х7 мм с 31 доступной линией ввода-вывода, а в CC2650MODA установлен CC2650 в корпусе QFN 5х5 мм с 15-ю доступными линиями (таблица 2).

Таблица 2. Выводы модуля CC2650MODA

Имя вывода Номер Тип Описание
DIO_0 4 Цифровой GPIO, SensorController
DIO_1 5 Цифровой GPIO, SensorController
DIO_2 6 Цифровой GPIO, SensorController, выход с высокой нагрузочной способностью
DIO_3 7 Цифровой GPIO, SensorController, выход с высокой нагрузочной способностью
DIO_4 8 Цифровой GPIO, SensorController, выход с высокой нагрузочной способностью
DIO_5/JTAG_TDO 11 Цифровой GPIO, SensorController, выход с высокой нагрузочной способностью
DIO_6/JTAG_TDI 12 Цифровой GPIO, SensorController, выход с высокой нагрузочной способностью
DIO_7 14 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
DIO_8 15 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
DIO_9 16 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
DIO_10 17 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
DIO_11 18 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
DIO_12 19 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
DIO_13 20 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
DIO_14 21 Цифровой, аналоговый вход GPIO, SensorController, аналоговый вход
JTAG_TCK 10 Цифровой JTAG
JTAG_TMS 9 Цифровой JTAG
nRESET 13 Цифровой вход Вывод сброса
EGP Питание Общий вывод, минус питания
GND 1, 3, 25 Питание Общий вывод, минус питания
VDD 22, 25 Питание Плюс питания

В документе [6] рекомендуется делать правки в заголовочных файлах для коррекции настроек – в CC2650_LAUNCHXL.h для LaunchPad и в CC2650STK.h для SensorTag. Указанные в [6] настройки адаптированы для отладочной платы CC2650 Module BoosterPack (осуществлена привязка к периферийным устройствам на плате), и при необходимости могут быть дополнены.

Соберем макет BLE-маячка на CC2650MODA с датчиком температуры и влажности HDC1080. Схема соединений будет предельно простой – на модуль и датчик достаточно подвести линии питания, подтянуть вывод сброса модуля, а также подтянуть к питанию линии SDA и SCL датчика (рисунок 10).

Большим плюсом контроллеров семейства CC26xx/CC13xx является возможность практически произвольной привязки сигналов встроенных периферийных устройств (I²C, SPI, таймеров и так далее) к внешним выводам.

В рассматриваемом примере линии SDA и SCL датчика подключаются в линиям DIO_0 и DIO_1 модуля. Модифицируем рассмотренный выше код BLE-маячка для SensorTag с внешним HDC1080 на связку CC2650MODA + HDC1080.

Рис. 10. Схема подключения HDC1080 к CC2650MODA

Рис. 10. Схема подключения HDC1080 к CC2650MODA

Следуя рекомендациям [6], изменим конфигурацию платы в файлах CC2650STK.h и CC2650STK.c.

Программный проект базируется на ProjectZero, следовательно, кроме шины I²C для работы с датчиком, в нем будут задействованы две линии на выход и две линии на вход (подключение светодиодов и кнопок соответственно). Для сохранения совместимости и будущих расширений функциональности настроим по две линии на выход и вход, остальные внешние линии не будут задействованы. Также надо учесть тот факт, что CC2650MODA выполнен на контроллере в другом корпусе, отличном от SensorTag.

Прежде всего следует немного подкорректировать проект для SC. В общих настройках проекта нужно выбрать в поле Chip package корпус QFN32 5×5 RHB (рисунок 11). В меню I/O Mapping установить для I²C SDA – DIO_0, для I²C SCL – DIO_1 (рисунок 12).

Рис. 11. Изменение общих настроек проекта SCS под модуль CC2650MODA

Рис. 11. Изменение общих настроек проекта SCS под модуль CC2650MODA

Рис. 12. Настройка конфигурации выводов

Рис. 12. Настройка конфигурации выводов

Объединение схем, показанных на рисунках 9 и 10, позволит протестировать работу макета BLE-маячка в SCS (рисунок 13).

Рис. 13. Тестирование работы SC в связке CC2650MODA + HDC1080

Рис. 13. Тестирование работы SC в связке CC2650MODA + HDC1080

После успешного тестирования генерируется набор файлов для интеграции SC в основной проект, – Code Generator, – Generate driver source code. Полученные файлы копируются из папки проекта SCS в папку Application основного проекта.

Вторым этапом правится код основного проекта.

Участок кода в CC2650STK.h, отвечающий за назначение выводов и выбор модели контроллера, модифицируется: ненужные выводы отмечаются как PIN_UNASIGNED и задаются выводы для I²C и входов/выходов, указывается текущий тип корпуса.

//#define CC2650EM_7ID
#define CC2650EM_5XD /* RF Configuration for CC2650 Module */
/* Mapping of pins to board signals using general board aliases
* <board signal alias>    <pin mapping>
*/
/* Discrete outputs */
#define Board_STK_LED1      IOID_13//IOID_10
#define Board_STK_LED2      IOID_14
#define Board_BUZZER        PIN_UNASSIGNED//IOID_21
#define Board_LED_ON        1
#define Board_LED_OFF       0
#define Board_BUZZER_ON     1
#define Board_BUZZER_OFF    0
/* Discrete inputs */
#define Board_KEY_LEFT      IOID_11//IOID_0
#define Board_KEY_RIGHT     IOID_12//IOID_4
#define Board_RELAY         PIN_UNASSIGNED//IOID_3
/* Sensor outputs */
#define Board_MPU_INT       PIN_UNASSIGNED//IOID_7
#define Board_TMP_RDY       PIN_UNASSIGNED//IOID_1
/* I2C */
#define Board_I2C0_SDA0     IOID_0//IOID_5
#define Board_I2C0_SCL0     IOID_1//IOID_6
#define Board_I2C0_SDA1     PIN_UNASSIGNED//IOID_8
#define Board_I2C0_SCL1     PIN_UNASSIGNED//IOID_9
/* SPI */
#define Board_SPI_FLASH_CS  PIN_UNASSIGNED//OID_14
#define Board_SPI_DEVPK_CS  PIN_UNASSIGNED//IOID_20
#define Board_FLASH_CS_ON   0
#define Board_FLASH_CS_OFF  1
#define Board_DEVPK_CS_ON   1
#define Board_DEVPK_CS_OFF  0
#define Board_SPI0_MISO     PIN_UNASSIGNED//IOID_18
#define Board_SPI0_MOSI     PIN_UNASSIGNED//IOID_19
#define Board_SPI0_CLK      PIN_UNASSIGNED//IOID_17
#define Board_SPI0_CSN      PIN_UNASSIGNED
#define Board_SPI1_MISO     PIN_UNASSIGNED
#define Board_SPI1_MOSI     PIN_UNASSIGNED
#define Board_SPI1_CLK      PIN_UNASSIGNED
#define Board_SPI1_CSN      PIN_UNASSIGNED
/* UART when connected to SRF06EB */
#define Board_EB_UART_TX    PIN_UNASSIGNED//IOID_16
#define Board_EB_UART_RX    PIN_UNASSIGNED//IOID_17
/* Power control */
#define Board_MPU_POWER     PIN_UNASSIGNED//IOID_12
#define Board_MPU_POWER_ON  1
#define Board_MPU_POWER_OFF 0
/* Audio */
#define Board_MIC_POWER      PIN_UNASSIGNED//IOID_13
#define Board_MIC_POWER_ON   1
#define Board_MIC_POWER_OFF  0
#define Board_AUDIO_DI       PIN_UNASSIGNED//IOID_2
#define Board_AUDIO_CLK      PIN_UNASSIGNED//IOID_11
/* UART pins used by driver */
#define Board_UART_TX        PIN_UNASSIGNED//Board_DP5_UARTTX
#define Board_UART_RX        PIN_UNASSIGNED//Board_DP4_UARTRX

Также модифицируется таблица настроек выводов в файле CC2650STK.c – инициируются только нужные выводы.

const PIN_Config BoardGpioInitTable[ ] = {
Board_STK_LED1 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,  /* LED initially off     */
Board_STK_LED2 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,  /* LED initially off     */
Board_KEY_LEFT | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_BOTHEDGES | PIN_HYSTERESIS,     /* Button is active low  */
Board_KEY_RIGHT | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_BOTHEDGES | PIN_HYSTERESIS,    /* Button is active low  */
PIN_TERMINATE
};

Для уменьшения общего энергопотребления можно снизить частоту генерации широковещательных пакетов. Например, изменение строчки

// Advertising interval when device is discoverable (units of 625us, 160=100ms)
#define DEFAULT_ADVERTISING_INTERVAL     400//160

даст частоту выдачи пакетов в один раз в 250 мс (400 * 0,625 мс).

Поскольку скорость изменения температуры и влажности, особенно в помещении, относительно невелика, частоту опроса датчика также можно установить небольшую.

Строка

>scifStartRtcTicksNow(0x00040000);

в функции ProjectZero_init(void) задаст интервал опроса датчика в 4 секунды. Именно с такой частотой будет вызываться callback-функция processTaskAlert().

Добавление в блок данных широковещательного пакета поля с типом «имя устройства» позволяет выводить получаемые с датчика результаты в текстовом виде, то есть вместо имени устройства будут выводиться показания температуры и влажности.

Объявление структуры блока данных широковещательного пакета:

static uint8_t advertData[ ] =
{
// Flags; this sets the device to use limited discoverable
// mode (advertises for 30 seconds at a time) or general
// discoverable mode (advertises indefinitely), depending
// on the DEFAULT_DISCOVERY_MODE define.
   0x02, // length of this data
   GAP_ADTYPE_FLAGS,
   DEFAULT_DISCOVERABLE_MODE | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
   0x05, // length of this data
   GAP_ADTYPE_16BIT_MORE,
   0, //старший байт температуры
   0, //младший байт температуры
   0, //старший байт влажности
   0, //младший байт температуры
   13,
   GAP_ADTYPE_LOCAL_NAME_COMPLETE,
// поле имени в него будут записываться показания датчиков в текстовом виде
   'c', 'c', '2', '6', '5', '0', 'm', 'o', 'd', 'a', ' ', ' ',
// */
};

Вариант callback-функции processTaskAlert с записью текстового представления показаний в поле имени устройства:

static void processTaskAlert(void)
{
// Clear the ALERT interrupt source
   scifClearAlertIntSource();
   uint16_t Temp; //
   uint16_t Hum; //
// введем переменные для хранения показаний; так как датчики выдают показания с
// точностью до десятых и сотых долей, удобнее иметь переменные в формате
// плавающей точки
float HDCTemp;
float HDCHum;
// считываются данные, зарегистрированные SC
   Temp=scifTaskData.i2cLightSensor.output.temp2;
   Hum=scifTaskData.i2cLightSensor.output.hum2;
// пересчитываем показания в «нормальный» вид
   HDCTemp=(Temp*165.0)/65536.0 – 40.0;
   HDCHum=(Hum*100.0)/65536.0;
// вывод данных в двоичном виде
   advertData[5]=HI_UINT16(Temp);
   advertData[6]=LO_UINT16(Temp);
   advertData[7]=HI_UINT16(Hum);
   advertData[8]=LO_UINT16(Hum);
// вывод показаний в текстовом виде в поле «имя устройства»
// выводим знак
if(HDCTemp<0){advertData[11]='-'; HDCTemp=abs(HDCTemp);}
else{advertData[11]='+';};
// формируем текстовые строки
   uint16_t templ=HDCTemp*10;
   advertData[15]=templ%10 + 0x30; templ=templ/10;
   advertData[14]='.';
   advertData[13]=templ%10 + 0x30; templ=templ/10;
   advertData[12]=templ%10 + 0x30; //*/
   advertData[16]='C';
   advertData[17]=' ';
   templ=HDCHum*10;
   advertData[21]=templ%10 + 0x30; templ=templ/10;
   advertData[20]='.';
   advertData[19]=templ%10 + 0x30; templ=templ/10;
   advertData[18]=templ%10 + 0x30; //*/
   advertData[22]='%';
// Initialize Advertisement data
   GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData);
// Acknowledge the ALERT event
   scifAckAlertEvents();
} // processTaskAlert

Полученный финальный код проекта собирается и прошивается в модуль с подключенным датчиком. Теперь для внешних устройств имя BLE-маячка становится «динамическим» и представляет собой строку с текущей температурой и влажностью, зарегистрированной датчиком. На рисунке 14 представлен пример отображения данных при просмотре доступных Bluetooth устройств через программку-сканнер (BLE Scanner от Bluepixel Technologies LLP), и штатным системным поиском Android (смартфон Prestigio PSP3531DUO).

Рис. 14. Пример отображения данных при просмотре доступных Bluetooth-устройств

Рис. 14. Пример отображения данных при просмотре доступных Bluetooth-устройств

Осциллограммы тока потребления дают периодические пики в 5,6 мА каждые 250 мс длительностью порядка 5 мс и пики потребления тока при опросе HDC порядка 0,95 мА длительностью около 15 мс.

Приложение для поиска и считывания данных с BLE-маячка

BLE-маячки более рассчитаны на подключение к смартфону, нежели к компьютеру или ноутбуку (на данный момент весьма ограничено число машин с поддержкой интерфейса BLE, традиционно поддерживается «классический» Bluetooth). Для считывания данных на смартфон необходимо приложение, которое будет проверять окружение на наличие BLE-устройств и подключаться к ним. Для простых случаев развертывание всей мощи Android Studio и ей подобных сред представляется излишним. Ситуацию скрашивает приложение, называемое Evothings Studio, производства компании Evothings, которое позволяет разрабатывать мобильные HTML5- и JavaScript-приложения с возможностью живой перезагрузки, отладки, и с минимальными требованиями к памяти и «железу». С учетом применения Cordova BLE plugin, разработка BLE приложений стала практически минутным занятием.

Задача в нашем случае выглядит так: имеем BLE-маяк, который считывает температуру и влажность и передает ее в широковещательных пакетах (adv-пакетах). Необходимо реализовать приложение, которое будет считывать эти данные и представлять их в удобном виде.

Evothings Studio – настройка среды и подключение библиотек

Среда Evothings состоит из трех частей – Evothings Studio [7], устанавливаемой на ПК и служащей для редактирования и управления программными проектами, облака Evothings, работающего посредником, и клиентской части Evothings Viewer [8], устанавливаемой на смартфон и способной загружать и запускать на устройстве пользовательские приложения и их облака.

После скачивания и установки Evothings Studio на компьютер, а Evothings Viewer – на телефон (подходит Android 4.0.3 или более поздняя версия), необходимо связать эти две части. Для этого генерируем ключ в приложении и вводим его в Evothings Viever, после чего нажимаем “CONNECT” (рисунки 15 и 16). В данном примере использовался телефон на базе Android 5.0. Пользователям iOS пока что придется собрать подобное приложение из исходников, но компания Evothings усердно трудится над этим вопросом, и в скором времени ожидается появление Evothings Viewer в iTunes.

Рис. 15. Генерация одноразового ключа для соединения серверной и клиентской части Evothings

Рис. 15. Генерация одноразового ключа для соединения серверной и клиентской части Evothings

Рис. 16. Ввод одноразового ключа для клиентской части Evothings

Рис. 16. Ввод одноразового ключа для клиентской части Evothings

Создаем новый проект (рисунок 17).

Рис. 17. Создание нового проекта Evothings

Рис. 17. Создание нового проекта Evothings

Для того чтобы отображать графики влажности и температуры, мы будем использовать библиотеку p5.js. Скачиваем и распаковываем архив. В папке с нашим проектом создаем папку libraries и копируем туда файлы p5.js и p5.dom.js. В корневой папке проекта создаем файл scetch.js и редактируем index.html:

<!DOCTYPE html>
<html>
<head>
<meta charset=”UTF-8”>
<script>
if(window.hyper && window.hyper.log) { console.log = hyper.log }
</script>
<script src="libraries/p5.js" type="text/javascript"></script>
<script src="libraries/p5.dom.js" type="text/javascript"></script>
<script src="sketch.js" type="text/javascript"></script>
<script src="cordova.js"></script>
<script src="libs/jquery/jquery-3.1.1.min.js"></script>
<script src="libs/fastclick/fastclick.js"></script>
<script src="libs/mdl/material.js"></script>
<style> body {padding: 0; margin: 0;} canvas {vertical-align: top;} </style>
</head>
<body>
</body>
</html>

Поиск BLE-маяка и общение с ним

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

evothings.ble.startScan(
onDeviceFound,
onScanError)

Для нее определены две callback-функции. Первая вызывается по обнаружении устройства. Здесь мы можем выбрать нужное устройство и отсеять остальные. Внутри функции для определения нужного устройства мы можем использовать его адрес:

if (device.address == ‘#DeviceAddress’)
{
//SomeCode
}

имя:

if (device.kCBAdvDataLocalName == ‘#DeviceLocalName’)
{
//SomeCode
}

а также еще несколько параметров.

В нашем случае мы не имеем локального имени маяка, поэтому будем использовать адрес устройства. Для начала нам необходимо узнать его. Для этого просто выведем адреса всех найденных устройств в консоль (рисунок 18):

Рис. 18. Отладочное консольное окно Evothings

Рис. 18. Отладочное консольное окно Evothings

function onDeviceFound(device)
{
console.log( ‘address : ’ + JSON.stringify(device.address));
}

Используя этот адрес, мы можем выделить наше устройство из остальных при обнаружении нескольких BLE-устройств:

evothings.ble.startScan(
onDeviceFound,
onScanError);
//Эта функция вызывается при обнаружении устройств
//Здесь мы можем выделить нужное нам устройство
function onDeviceFound(device){
if(device.address == 'B0:B4:48:BE:CF:85'){
//Останавливаем поиск
evothings.ble.stopScan();
//Подключение
evothings.ble.connectToDevice(
device,
onConnected,
onDisconnected,
onConnectError);
}
}
//Вызывается при подключении к устройству
function onConnected(device){
console.log(‘connected to device’);
}
//Вызывается при отключении от устройства
function onDisconnected(device){
console.log(‘disconnected to device’);
}
//Вызывается при ошибке подключения
function onConnectedError(error){
console.log(‘Connect error’ + error);
}

По умолчанию функция evothings.ble.connectToDevice считывает все сервисы, характеристики и дескрипторы устройства, но можно выбрать определенный сервис для считывания, что приведет к увеличению производительности:

evothings.ble.connectToDevice(
device,
onConnected,
onDisconnected,
onConnectError)
{ serviceUUIDs: ['f000aa70-0451-4000-b000-000000000000'] })

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

Для чтения/записи характеристик необходимо использовать функции evothings.ble.getService и evothings.ble.getCharacteristic.

function onDeviceFound(device){
if(device.address == 'B0:B4:48:BE:CF:85'){
isScanning = false;
evothings.ble.connectToDevice(
device,
onConnected,
onDisconnected,
onConnectError);
}
}
function onScanError(error){
console.log('Scan error : ' + error);
}

В функции подключения к устройству вызывается функция evothings.ble.writeChracteristic, в которую необходимо передать адрес устройства, сервис, полученный через UUID сервиса, характеристику, а также полученные через UUID характеристики и массив данных, который необходимо записать.

function onConnected(device){
console.log('connected to device');
//UUID сервиса
var LEDservice = 'F0001110-0451-4000-B000-000000000000';
//UUID характеристики
var LEDCharConfig = 'F0001112-0451-4000-B000-000000000000';
//Получаем сервис
var service = evothings.ble.getService(device, LEDservice);
//Получаем характеристику
var config = evothings.ble.getCharacteristic(service,LEDCharConfig);
//Функция записи характеристики
evothings.ble.writeCharacteristic(
device,
config,
new Uint8Array([1]),
onActivated,
onActivateError);
}
//Вызывается при успешной записи
function onActivated(){
console.log('LED is on');
}
//Вызывается при ошибке записи
function onActivateError(error){
console.log('activate error : ' + error);
}
function onDisconnected(device){
console.log('disconnected from device');
}
function onConnectError(error){
console.log('error : ' + error);
}

В результате мы подаем питание на красный светодиод (рисунок 19).

Рис. 19. Управление светодиодом через Evothings-приложение

Рис. 19. Управление светодиодом через Evothings-приложение

Для чтения характеристик используется подобный метод:

//UUID сервиса
var DataService = 'F0001130-0451-4000-B000-000000000000';
//UUID характеристики
var DataCharacteristic = 'F0001131-0451-4000-B000-000000000000';
//Получаем сервис
var service = evothings.ble.getService(device, Dataservice);
//Получаем характеристику
var characteristic = evothings.ble.getCharacteristic(service, DataCharacteristic);
evothings.ble.readCharacteristic(
device,
characteristic,
onRead,
onReadError);
function onRead(data){
var raw = new DataView(data).getUint16(0,true);
console.log(‘ Raw value’ + raw);
}
function onReadError(error){
console.log(‘read error : ‘ + error);
}

Полученные данные являются массивом байт, который нужно перевести в понятный человеку вид.

Получение данных и построение графиков

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

  1. ищем устройства;
  2. считываем данные с нашего маяка, определенного по адресу;
  3. если значение температуры или влажности отличается от ранее полученных значений, заносим их в массивы;
  4. рисуем график;
  5. повторяем.
var data; // переменная, с помощью которой мы будем отслеживать изменение данных
var tempPoints = [ ]; //массив точек температуры
var humPoints = [ ]; //массив точек влажности
var textDisplay;

Функция setup() выполняется только один раз и служит для инициализации. Функция draw() выполняется каждый кадр. В ней мы рисуем график.

function setup(){
textDisplay = new TextData();
createCanvas(windowWidth. windowHeight);
evothings.ble.startScan(
onDeviceFound,
onScanError);
}

В функции draw() сначала фон заполняется выбранным цветом, затем рисуется сетка графика, поверх этого рисуются линии, которые соединяют точки температуры и влажности, образуя линейный график. В конце на экран выводятся последние полученные температура и влажность с помощью функции textD.show(). В этой же функции, при желании, можно считать и вывести среднее значение температуры и влажности за определенный интервал времени или за определенное количество точек. Функции push() и pop() нужны для того чтобы настройки отображения (толщина и цвет линий, заполнение, угол поворота и так далее) применялись исключительно для элементов, которые отображаются только между этими функциями.

function draw(){
background(51);
drawGraph(); //отображение сетки графика
for(var i = 0; i < tempPoints.length; i++){
if(tempPoints[i+1] != null){ //соединяем точки температуры
push();
stroke(200,15,54);
strokeWeight(4);
line(tempPoints[i].x, tempPoints[i].y, tempPoints[i+1].x, tempPoints[i+1].y);
pop();
tempPoints[i].show();
}
if (humPoints[i+1] != null){ //соединяем точки влажности
push();
stroke(15,105,154);
strokeWeight(4);
line(humPoints[i].x, humPoints[i].y, humPoints[i+1].x, humPoints[i+1].y);
pop();
humPoints[i].show();
}
}
textD.show(); // отображение текущей температуры и влажности
}

Функции PointTemp и PointHum служат для создания класса точки. Входное значение value пропорционально переносится из текущего диапазона значений (fromLow…fromHight) в новый диапазон (toLow…toHight) с помощью функции map (value, fromLow, fromHight, toLow, toHight), это позволит корректно отображать графики на устройствах любой диагонали

function PointTemp(value){
this.x = 0;
this.y = map(value, -50, 100, height/2, 20);
this.show = function(){
push();
fill(255,234,76);
noStroke();
ellipse(this.x, this.y,4);
pop();
}
}
function PointHum(value){
this.x = 0;
this.y = map(value, 0, 100, height, height/2 + 20);
this.show = function(){
push();
fill(155, 24, 215);
noStroke();
ellipse(this.x, this.y, 4);
pop();
}
}

Функция TextData выводит последние полученные значения температуры и влажности в текстовом виде:

function TextData(){
this.update = function(temp, hum){
this.temp = temp;
this.hum = hum;
}
this.show = function(){
push();
fill(152,134,116);
textSize(15);
textAlign(LEFT);
text('Current Temp : ' + this.temp, 0 , 10);
text('Current hummidity : ' + this.hum, 0 , height/2);
pop();
}
}

Функция drawGraph рисует сетку для удобного чтения графика. Здесь также используется функция map(), для корректного отображения сетки на всех диагоналях экранов. Грубо говоря, мы делим верхнюю половину экрана на 150 частей для отображения температуры -50…100°С, нижнюю половину экрана — на 100 частей для отображения влажности 0…100%, и рисуем одну горизонтальную линию на каждые 10 частей.

function drawGraph(){
push();
strokeWeight(1);
stroke(255,100);
fill(255,100);
textSize(5);
for(var i = -40; i < 100; i +=10){
var y = map(i, -50, 100, height/2, 20);
line(0,y,width,y);
text(i,0,y-4);
}for(var i = 10; i < 100; i+= 10){
var y = map(i, 0, 100, height, height/2 + 20);
line(0,y,width,y);
text(i,0,y-4);
}
}

После обнаружения нужного устройства считываем его широковещательный пакет. Для перевода данных из Base64 в HEX используем функцию b64ToHex. Далее выделяем из строки данные о температуре и влажности. При получении новой точки сдвигаем все остальные по шкале Х и добавляем в массив новую точку. При этом если точка уходит за пределы экрана, ее необходимо удалить из массива.

//При обнаружении устройства
function onDeviseFound(device){
// при нахождении нужного нам – прекращаем поиск
if(device.address == 'B0:B4:48:BE:CF:85'){
var strData = b64ToHEX(device.scanRecord).substr(0, 18);
if(data != strData){  //Если текущее значение пакета отличается от предыдущего - то добавляем новую точку
for(var i = tempPoints.length -1; i >= 0; i--){
tempPoints[i].x += 5; // сдвигаем предыдущие точки
if(tempPoints[i].x > width){
tempPoints.splice(i,1);
//если точка уходит за пределы экрана - то удаляем ее
}
humPoints[i].x += 5;
if(humPoints[i].x > width){
humPoints.splice(i,1);
}
}
data = strData;
//Получаем значение температуры
var strTemp = data.substr(10, 4);
//в пакете значения температуры хранятся в 11,12,13,14 байтах
var temp = parseInt(strTemp, 16); // переводим из HEX в DEC
temp = (temp / 65536) * 165 – 40;
//получаем температуру в градусах Цельсия
//Получаем значение влажности воздуха
var strHum = data.substr(14, 4); //значение влажности – 15,16,17,18 байт
var hum = parseInt(strHum, 16); // переводим из HEX в DEC
hum = (hum / 65536) * 100; //получаем влажность в процентах
console.log('=================================');
console.log('TEMP : ' + JSON.stringify(temp));
console.log(' HUM : ' + JSON.stringify(hum));
console.log('=================================');
tempPoints.push(new PointTemp(temp)); //добавляем новые точки в массив
humPoints.push(new PointHum(hum));
textD.update(temp, hum);
}
}
}
//При ошибке поиска
function onScanError(error){
console.log('Scan error: ' + error);
}
//При подключении
function onConnected(device){
console.log('Connected to device');
}
//При отключении от устройства
function onDisconnected(device){
console.log('Disconected from device');
}
//При ошибке подключения
function onConnectError(error){
console.log('Connect error: ' + error);
}
function b64ToHEX(base64){ //функция перевода base64 to HEX
var raw = atob(base64);
var HEX = '';
for(var i = 0; i < raw.length; i++){
var _hex = raw.charCodeAt(i).toString(16);
HEX +=(_hex.length == 2 ? _hex : '0' + _hex);
}
return HEX.toUpperCase();
}

В результате получаем следующее (рисунок 20).

Рис. 20. Отрисовка графиков на экране устройства

Рис. 20. Отрисовка графиков на экране устройства

Заключение

SensorTag представляет собой достаточно удобную платформу для прототипирования BLE-устройств. Миграция кода внутри одного семейства также не представляет особых проблем – изменения в коде незначительны и касаются, в основном, корректной конфигурации и распределения выводов.

Модуль CC2650MODA в большинстве случаев позволяет существенно сократить время на разработку BLE-устройств и обладает такими преимуществами как компактные размеры, встроенная антенна, удобная распиновка и шаг выводов.

Adv-пакеты BLE-маячков и BLE-устройств, ожидающих подключения, доступны всем прослушивающим устройствам. Логического подключения к BLE-маяку, как правило, устанавливать не требуется. Маячки предназначаются для трансляции в эфир информации, которая может быть доступна всем заинтересованным в ней, это может быть рекламная информация, информация справочного характера, данные окружающей среды (температура, влажность, освещенность, давление и тому подобное).

Трансляция в adv-пакетах данных в текстовом виде (ASCII-строки) позволяет сделать информацию доступной и без специализированных программ.

Если у вас нет желания изучать JAVA и особенности работы с Bluetooth для подобного рода задач, то Evothings Studio совместно с Cordova BLE plugin являются лучшим выбором. Понятные функции и язык JavaScript, который прост в изучении, делают подобную работу несложным и интересным занятием. Помимо BLE имеется множество других IoT-устройств, работа с которыми также может быть упрощена с помощью средств Evothings.

Литература

  1. Йоаким Линд. Маячки Bluetooth Low Energy
  2. Александр Калачев. CC26хх/СС13хх – от созерцания к практике
  3. HDC1000 Low Power, High Accuracy Digital Humidity Sensor with Temperature Sensor
  4. HDC1080 Low Power, High Accuracy Digital Humidity Sensor with Temperature Sensor
  5. CC2650MODA SimpleLink™ Bluetooth® low energy Wireless MCU Module
  6. Nathan Siegel. Using TI Certified Bluetooth® low energy Module (CC2650MODA) as Single-Chip Wireless MCU
  7. Evothings _ Making mobile apps for IoT easy, fast and fun to build!
  8. Evothings Viewer
О компании Texas Instruments

В середине 2001 г. компании Texas Instruments и КОМПЭЛ заключили официальное дистрибьюторское соглашение, которое явилось результатом длительной и успешной работы КОМПЭЛ в качестве официального дистрибьютора фирмы Burr-Brown. (Как известно, Burr-Brown вошла в состав TI так же, как и компании Unitrode, Power Trend и Klixon). С этого времени компания КОМПЭЛ получила доступ к поставке всей номенклатуры производимых компанией TI компонентов, ...читать далее

Наличие на складах
Наименование Наличие Цена
HDC1000YPAR (TI) 16 952 9.2220 $ 556.33 руб. от 500 шт
CC2650F128RGZT (TI) 6 958 4.1824 $ 252.32 руб. от 120 шт
OPT3001DNPT (TI) 9 036 1.5704 $ 94.74 руб. от 319 шт
TMP007EVM (TI) 102 220.16 $ 13282 руб. от 1 шт
HDC1080DMBT (TI) 20 505 2.6106 $ 157.49 руб. от 192 шт
CC2650MODAMOHT (TI) 5 007 9.3376 $ 563.31 руб. от 54 шт
CC1310F128RHBT (TI) 7 609 4.8591 $ 293.14 руб. от 103 шт