Всем привет! Во второй части статьи был приведён пример коммуникаций между Android и nRF52832 при наличии между ними соединения, т.е. по протоколу GATT. Однако, для случаев, когда достаточно односторонней передачи медленно меняющихся данных, можно ограничиться протоколом GAP и отправлять данные в составе широковещательных пакетов.
Содержание статьи / Table Of Contents
↑ Видео-демонстрация проекта
Как видите, отчётливо просматривается запаздывание реакции Android на изменение состояния светодиода и кнопок. Связано это с отсутствием синхронизации между центральным и периферийным устройствами. Поэтому, в широковещательных пакетах предпочтительно передавать данные, значение которых меняется значительно реже сканирования центральным устройством.
↑ Код для nRF52832
Программа с периодом в 300 мс отправляет в эфир широковещательный пакет с информацией о состоянии светодиода (пин P0.17) и двух кнопок (пины P0.13 и P0.14), за управление которыми ответственны библиотеки led#ifndef LED_H_
#define LED_H_
#include "nrf.h"
#define LED 17
#define LED_OFF NRF_GPIO->OUT |= (1UL << LED)
#define LED_ON NRF_GPIO->OUT &= ~(1UL << LED)
#define LED_IS_OFF (NRF_GPIO->OUT & (1UL << LED))
#define LED_IS_ON !LED_IS_OFF
void ledInit();
extern unsigned char ledCounter;
#endif /* LED_H_ */
#include "led.h"
unsigned char ledCounter = 0;
void ledInit()
{
NRF_GPIO->PIN_CNF[LED] = (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
LED_OFF;
}
и buttons
#ifndef BUTTONS_H_
#define BUTTONS_H_
#include "nrf.h"
#define BUTTON_1_PUSHED (NRF_GPIO->IN & (GPIO_IN_PIN14_High << GPIO_IN_PIN14_Pos)) == 0
#define BUTTON_1_RELEASED (NRF_GPIO->IN & (GPIO_IN_PIN14_High << GPIO_IN_PIN14_Pos)) != 0
#define BUTTON_2_PUSHED (NRF_GPIO->IN & (GPIO_IN_PIN13_High << GPIO_IN_PIN13_Pos)) == 0
#define BUTTON_2_RELEASED (NRF_GPIO->IN & (GPIO_IN_PIN13_High << GPIO_IN_PIN13_Pos)) != 0
void buttonsInit();
#endif
#include "buttons.h"
void buttonsInit()
{
NRF_GPIO->PIN_CNF[13] = (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | (GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos);
NRF_GPIO->PIN_CNF[14] = (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | (GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos);
}
В качестве шаблона main.c принят файл из примера beacon SDK 17. В дополнение к имеющимся данным (тип устройства, длина данных, UUID, RSSI) широковещательный пакет содержит три байта данных о состоянии светодиода и кнопок.
static uint8_t m_beacon_info[APP_BEACON_INFO_LENGTH] =
{
APP_DEVICE_TYPE, // Device type.
APP_ADV_DATA_LENGTH, // Length of the manufacturer specific data.
APP_BEACON_UUID, // 128 bit UUID value.
0, // Empty byte
0, 0, 0, // Led and Buttons states
APP_MEASURED_RSSI // TX power.
};
Кроме того, в функцию timers_init добавлены фрагменты инициализации и включения таймера, который с вышеуказанным периодом вызывает обработчик adv_data_update:
static void timers_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
err_code = app_timer_create(&adv_data_update_timer_id, APP_TIMER_MODE_REPEATED, adv_data_update);
APP_ERROR_CHECK(err_code);
err_code = app_timer_start(adv_data_update_timer_id, APP_TIMER_TICKS(ADV_DATA_UPDATE_TIME), NULL);
APP_ERROR_CHECK(err_code);
}
А также прописан код самого обработчика, который:
• с периодом в 1.5 с меняет состояние светодиода на противоположное,
• проверяет состояние кнопок,
• приостанавливает широковещание,
• обновляет данные в m_beacon_info,
• возобновляет широковещание.
static void adv_data_update(void * p_context)
{
ret_code_t err_code;
sd_ble_gap_adv_stop(m_advertising.adv_handle);
ledCounter++;
if(ledCounter == 5)
{
ledCounter = 0;
if(LED_IS_OFF)
{
LED_ON;
NRF_LOG_INFO("led ON")
m_beacon_info[19] = 1;
}
else if(LED_IS_ON)
{
LED_OFF;
m_beacon_info[19] = 0;
NRF_LOG_INFO("led OFF");
}
}
if(BUTTON_1_PUSHED)
{
m_beacon_info[20] = 1;
NRF_LOG_INFO("button 1 pushed");
}
else if(BUTTON_1_RELEASED)
{
m_beacon_info[20] = 0;
NRF_LOG_INFO("button 1 released");
}
if(BUTTON_2_PUSHED)
{
m_beacon_info[21] = 1;
NRF_LOG_INFO("button 2 pushed");
}
else if(BUTTON_2_RELEASED)
{
m_beacon_info[21] = 0;
NRF_LOG_INFO("button 2 released");
}
err_code = ble_advdata_encode(&advdata, m_adv_data.adv_data.p_data, &m_adv_data.adv_data.len);
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params);
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
APP_ERROR_CHECK(err_code);
}
Основная функция инициализирует устройство и запускает широковещание:
int main(void)
{
log_init();
ledInit();
buttonsInit();
timers_init();
power_management_init();
ble_stack_init();
advertising_init();
advertising_start();
while(1)
{
idle_state_handle();
}
}
↑ Android-приложение
В манифесте прописаны разрешения на работу с Bluetooth.<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Gap"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
В xml-файле определены параметры:
• кнопки сканирования BLE-устройств,
• текстовых полей, отражающих состояния светодиода и кнопок nRF52832.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/ledTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="led off"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.492"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.442" />
<TextView
android:id="@+id/button1TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="button 1 is released"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.496"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.587" />
<TextView
android:id="@+id/button2TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="button 2 is released"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.496"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.753" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="scan"
android:text="Scan"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.495"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.279" />
</androidx.constraintlayout.widget.ConstraintLayout>
Java-код:
а) По нажатию кнопки сканирует BLE-устройства.
public void scan(View view) {
if (scanner != null) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
scanner.startScan(filters, scanSettings, scanCallback);
Toast.makeText(this, "scanning...", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "could not get scanner object", Toast.LENGTH_LONG).show();
}
}
б) Отражает в текстовых полях актуальные данные по состоянию светодиода и кнопок.
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
device = result.getDevice();
Toast.makeText(MainActivity.this, "device with address" + "\n" + device.getAddress() + "\n" + "found", Toast.LENGTH_LONG).show();
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
}
byte[] data = result.getScanRecord().getManufacturerSpecificData(89);
if(data[19] == 1) {
ledTextView.setText("led on");
}
else if(data[19] == 0) {
ledTextView.setText("led off");
}
if(data[20] == 1) {
button1TextView.setText("button 1 is pressed");
}
else if(data[20] == 0) {
button1TextView.setText("button 1 is released");
}
if(data[21] == 1) {
button2TextView.setText("button2 is pressed");
}
else if(data[21] == 0) {
button2TextView.setText("button2 is released");
}
}
};
↑ Файлы
🎁Исходники android.zip 10.64 Mb ⇣ 12🎁Исходники nrf52832.zip 50.36 Kb ⇣ 9
Спасибо за внимание!