Kurs Nordic nRF z BT (4). Bluetooth LE

Kurs Nordic nRF z BT (4). Bluetooth LE

W poprzedniej części kursu zajęliśmy się sterowaniem LED-ami naszej płytki deweloperskiej. Dziś dodamy możliwość ustawiania przez smartfon parametrów ich świecenia. Zaprezentujemy również trochę teorii dotyczącej Bluetooth Low Energy.

Bluetooth LE

Na płytce nRF5340DK mamy zamontowany SoC nRF5340, który zawiera dwa procesory Arm® Cortex®-M33: jeden aplikacyjny i jeden sieciowy. Do tej pory działaliśmy na procesorze aplikacyjnym, a teraz zaprzęgniemy procesor sieciowy do obsługi stosu protokołu Bluetooth Low Energy (Bluetooth LE, kolokwialnie BLE).

Bluetooth LE różni się zasadniczo od klasycznego Bluetooth. Powstał w odpowiedzi na rosnące zapotrzebowanie na technologie bezprzewodowe o niskim zużyciu energii, szczególnie w aplikacjach IoT, urządzeniach noszonych (wearables), czujnikach i innych urządzeniach o małej mocy. Tradycyjny Bluetooth (BR/EDR) nie był zoptymalizowany pod kątem energooszczędności, co ograniczało jego zastosowanie w takich aplikacjach. BLE ma, co prawda, mniejszy transfer danych (do 1,4 Mbps), ale oferuje dłuższy czas pracy na baterii.

Ponadto Bluetooth LE – poza połączeniami Point-to-Point – potrafi pracować w topologiach Broadcast i Mesh. Pozwala to budować urządzenia, które po prostu rozgłaszają dane, a nawet budować całe sieci urządzeń oparte na BLE.

Tradycyjny Bluetooth i BLE nie są ze sobą kompatybilne. Ponieważ nRF5340 wspiera jedynie Bluetooth LE, do sterowania naszą płytką jest wymagany smartfon lub tablet wspierający tę technologię. Większość urządzeń wyprodukowanych po 2011 roku powinno to wsparcie oferować.

Przedstawimy teraz nieco podstawowych pojęć związanych z BLE. Nie są one kluczowe dla naszego celu, ale powinny znacząco ułatwić zgłębianie tematu i czytanie dokumentacji.

Urządzenia BLE pracują z reguły w jednej z dwóch ról: Central lub Peripheral.

Central skanuje i inicjuje połączenia z urządzeniami typu Peripheral. Pełni funkcję hosta, odpowiada za typowe zadania tej roli, takie jak nawiązywanie połączeń i zarządzanie nimi oraz przetwarzanie danych. W naszym przypadku smartfon będzie pracował w tym trybie.

Peripheral to urządzenie, które rozgłasza swoje dane, umożliwia nawiązanie połączenia oraz udostępnia jeden lub więcej serwisów (Service). Pełni funkcję serwera. Urządzenia IoT o ograniczonych zasobach, które wymagają niskiego zużycia energii, zazwyczaj odgrywają właśnie tę rolę – w tym kursie funkcję Peripheral będzie pełniła nasza płytka deweloperska.

W przypadku pracy rozgłoszeniowej (Broadcast oriented) role są podobne: zamiast Peripheral mamy Broadcaster, a zamiast Central jest Observer, z tą różnicą, że pierwszy nie udostępnia, a drugi nie nawiązuje połączenia.

Jedno urządzenie może jednocześnie pełnić więcej niż jedną funkcję: udostępniać dane więcej niż jednemu urządzeniu Central i odbierać dane z wielu urządzeń typu Peripheral.

Wymiana danych w BLE odbywa się w ramach Serwisów i Charakterystyk.

Charakterystyka (Characteristic) to podstawowy fragment informacji/danych, który serwer (Peripheral) udostępnia klientowi (Central). Charakterystyka jest zawsze częścią serwisu. W naszym przypadku będzie to parametr pracy LED-a.

Serwis (Service) to po prostu grupa charakterystyk. Z reguły łączą się one w logiczną całość – np. temperatura, wilgotność i jakość powietrza. My utworzymy jeden serwis związany ze sterowaniem LED-em.

W poprzednim odcinku…

…stworzyliśmy aplikację, w której mogliśmy włączać, wyłączać oraz migać diodą naszej płytki deweloperskiej nRF5340-DK za pomocą przycisków.

Projekt składał się z trzech plików źródłowych (listingi 1...3). Dodatkowo mieliśmy plik CMakeLists.txt do budowania projektu (listing 4), oraz konfigurację projektu w pliku prj.conf (listing 5). Wszystkie pliki źródłowe wymienione w tym odcinku kursu można znaleźć w materiałach dodatkowych do artykułu na stronie https://ep.com.pl.

#include "led_control.h"
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);

static const struct gpio_dt_spec buttons[] = {
GPIO_DT_SPEC_GET(DT_NODELABEL(button0), gpios),
GPIO_DT_SPEC_GET(DT_NODELABEL(button1), gpios),
GPIO_DT_SPEC_GET(DT_NODELABEL(button2), gpios),
GPIO_DT_SPEC_GET(DT_NODELABEL(button3), gpios)
};

static struct gpio_callback cb_data;

static void button_callback(const struct device *dev,
struct gpio_callback *cb, uint32_t pins)
{
LOG_INF("Pin mask 0x%08x", pins);

if (pins & BIT(buttons[0].pin)) {
led_send((led_msg){ LED_STATE, 0 });
} else if (pins & BIT(buttons[1].pin)) {
led_send((led_msg){ LED_STATE, 1 });
} else if (pins & BIT(buttons[2].pin)) {
led_send((led_msg){ LED_BLINK, 500 });
} else if (pins & BIT(buttons[3].pin)) {
led_send((led_msg){ LED_BLINK, 100 });
}
}

int main(void) {
gpio_init_callback(&cb_data, button_callback,
BIT(buttons[0].pin) | BIT(buttons[1].pin) |
BIT(buttons[2].pin) | BIT(buttons[3].pin));

for (int i = 0; i < ARRAY_SIZE(buttons); ++i) {
gpio_pin_configure_dt(&buttons[i], GPIO_INPUT);
gpio_pin_interrupt_configure_dt(&buttons[i],
GPIO_INT_EDGE_FALLING);
gpio_add_callback(buttons[i].port, &cb_data);
}

int counter = 0;
while (1) {
LOG_INF("Tick %d", counter++);
k_msleep(1000);
}
return 0;
}

Listing 1. Plik src/main.c
#pragma once

typedef struct {
enum {
LED_STATE, // param: 0 (off) or 1 (on)
LED_BLINK // param: time [ms] for every toggle
} command;
unsigned int param;
} led_msg;

void led_send(led_msg msg);

Listing 2. Plik src/led_control.h
#include "led_control.h"
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>


LOG_MODULE_REGISTER(led_control, LOG_LEVEL_DBG);

static const struct gpio_dt_spec led =
GPIO_DT_SPEC_GET(DT_NODELABEL(led0), gpios);

K_MSGQ_DEFINE(led_queue, sizeof(led_msg), 10, 1);

void led_send(led_msg msg) {
k_msgq_put(&led_queue, &msg, K_NO_WAIT);
}

static void led_worker(void *a, void *b, void *c) {
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);

led_msg msg;
k_timeout_t timeout = K_FOREVER;

while (1) {
if (k_msgq_get(&led_queue, &msg, timeout) == 0) {
if (msg.command == LED_STATE) {
LOG_INF("LED_STATE %d received", msg.param);
gpio_pin_set_dt(&led, msg.param);
timeout = K_FOREVER;
} else if (msg.command == LED_BLINK) {
LOG_INF("LED_BLINK %dms received", msg.param);
timeout = K_MSEC(msg.param);
}
} else {
gpio_pin_toggle_dt(&led);
}
}
}

K_THREAD_DEFINE(led_thread, 500, led_worker, NULL, NULL, NULL,
K_HIGHEST_THREAD_PRIO, 0, 0);


#ifdef CONFIG_SHELL

#include <zephyr/shell/shell.h>
#include <stdlib.h>

static int cmd_led_on(const struct shell *shell, size_t argc, char **argv) {
led_send((led_msg){ LED_STATE, 1 });
shell_print(shell, "LED on");
return 0;
}

static int cmd_led_off(const struct shell *shell, size_t argc, char **argv) {
led_send((led_msg){ LED_STATE, 0 });
shell_print(shell, "LED off");
return 0;
}

static int cmd_led_blink(const struct shell *shell, size_t argc, char **argv) {
if (argc != 2) {
shell_print(shell, "led blink <time in ms>");
return -EINVAL;
}

uint32_t period = strtoul(argv[1], NULL, 10);
led_send((led_msg){ LED_BLINK, period });
shell_print(shell, "LED blinking period %d ms:",period);
return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(led_menu,
SHELL_CMD(on, NULL, "LED on", cmd_led_on),
SHELL_CMD(off, NULL, "LED off", cmd_led_off),
SHELL_CMD(blink, NULL, "LED blink with period [ms]", cmd_led_blink),
SHELL_SUBCMD_SET_END
);

SHELL_CMD_REGISTER(led, &led_menu, "LED", NULL);
#endif

Listing 3. Plik src/led_control.c
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

project(thread_test)

target_sources(app PRIVATE
src/main.c
src/led_control.c
)

Listing 4. Plik /CMakeLists.txt
CONFIG_GPIO=y

CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y

CONFIG_LOG=y
CONFIG_LOG_PROCESS_THREAD_SLEEP_MS=100

CONFIG_USE_SEGGER_RTT=y
CONFIG_LOG_BACKEND_RTT=y

CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_SERIAL=y

CONFIG_GPIO_SHELL=y

Listing 5. Plik /prj.conf

Włączamy Bluetooth

Ustaliliśmy już, że nasza płytka będzie pracowała w standardzie BLE jako Peripheral. Należy zatem włączyć taką możliwość w prj.conf – pliku konfiguracyjnym projektu – poprzez dodanie trzech linii, widocznych na listingu 6.

CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="Led control"

Listing 6. Włączanie BLE w pliku prj.conf

Włączenie i rozpoczęcie rozgłaszania BLE obsłuży nowy plik src/bt_control.c (listing 7).

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include "bt_control.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_control, LOG_LEVEL_DBG);

// advertisement data (AdvData)
static const struct bt_data adv_data[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, (sizeof(CONFIG_BT_DEVICE_NAME)-1)),
};

static void connected_cb(struct bt_conn *conn, uint8_t err)
{
if (err) {
LOG_ERR("Connection failed. error:%u", err);
return;
}

struct bt_conn_info info;
int ret = bt_conn_get_info(conn, &info);
if (ret == 0) {
char addr_dst[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(info.le.dst, addr_dst, sizeof(addr_dst));
LOG_INF("Connected to: %s", addr_dst);
} else {
LOG_ERR("MAC read failed");
}
}

static void disconnected_cb(struct bt_conn *conn, uint8_t reason)
{
LOG_INF("Disconnected. Reason: %u", reason);
}

static struct bt_conn_cb conn_callbacks = {
.connected = connected_cb,
.disconnected = disconnected_cb
};

void bt_ready_cb(int status) {

//MAC
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
size_t count = CONFIG_BT_ID_MAX;
bt_id_get(addrs, &count);

//print MAC
if (count > 0) {
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(&addrs[0], addr_str, sizeof(addr_str));
LOG_DBG("BLE MAC: %s", addr_str);
} else {
LOG_ERR("Error while getting MAC");
}

//Start advertising
int err = bt_le_adv_start(BT_LE_ADV_CONN, adv_data, ARRAY_SIZE(adv_data), NULL, 0);
if (err) {
LOG_ERR("BLE advertisement error: %d", err);
} else {
LOG_DBG("BLE advertisement started");
}

//Register connected/disconnected callbacks
bt_conn_cb_register(&conn_callbacks);
}

int bt_control_init(void) {

LOG_DBG("BLE init...");

int ret = bt_enable(bt_ready_cb);
if (ret) {
LOG_ERR("BLE init error: %d\n", ret);
return ret;
}

return ret;
}
Listing 7. Plik src/bt_control.c

Plik nagłówkowy (src/bt_control.h) będzie bardzo prosty (listing 8).

#pragma once

int bt_control_init(void);

Listing 8. Plik nagłówkowy src/bt_control.h

Funkcja bt_control_init() powinna być wywołana w funkcji main(), po uprzednim inkludowaniu pliku nagłówkowego bt_control.h. Ma za zadanie jedynie wywołać Zephyrową funkcję bt_enable() – która rozpocznie proces włączania funkcjonalności Bluetooth. Gdy proces się zakończy, automatycznie wywołana zostanie bt_ready_cb().

Funkcja bt_ready_cb() ma z kolei wyświetlić na konsoli adres MAC płytki oraz rozpocząć rozgłaszanie, aby można było odnaleźć płytkę za pomocą skanowania w smartfonie.

Na końcu rejestrowane są funkcje, które wywołane będą w przypadku podłączenia się i odłączenia się smartfona od nRF5340-DK.

Należy jeszcze pamiętać, aby dodać plik src/bt_led_svc.c do build systemu w pliku CMakeLists.txt.

Zbudujmy projekt i wgrajmy kod na płytkę

Aby nieco uprościć pracę, będziemy używać natywnej konfiguracji płytki nRF5340-DK. Wybierając konfigurację budowania przez przycisk Add build configuration, należy wybrać nrf5340dk_nrf5340_cpuapp lub – jeśli budujecie projekt poprzez konsolę – należy wpisać `west build -b nrf5340dk_nrf5340_cpuapp`. Pliki z poprzedniego odcinka kursu z katalogu boards nie będą nam potrzebne.

Jak już wspomnieliśmy, projekt na SoC nRF5340 z BLE używa obu procesorów. Jak zatem oprogramować procesor sieciowy i wgrać na niego firmware?

W niektórych (starszych) kursach lub wątkach w Internecie pojawia się polecenie, aby najpierw skompilować i wgrać firmware obsługujący BLE na procesor sieciowy, a potem – swój aplikacyjny firmware na procesor aplikacyjny. Nie jest to obecnie wymagane, gdyż narzędzie west zbuduje dla Was plik hex zawierający firmware obu procesorów i automatycznie wgra oba.

Pod koniec budowania w konsoli można zobaczyć wpis „Generating zephyr/merged_domains.hex”. Tu właśnie jest tworzony plik z firmware dla obu procesorów.

Włączenie opcji CONFIG_BT=y w ustawieniach projektu automatycznie dodaje do projektu budowanie drugiego obrazu, przeznaczonego dla procesora sieciowego, zawierającego wszystkie potrzebne warstwy stosu BLE. W katalogu /build/hci_ipc odnajdziesz wynik budowania projektu na procesor sieciowy.

Tego typu projekty są całkiem nieźle wspierane przez wtyczkę nRF Connect. Wystarczy, że w sekcji Applications wybierzesz procesor sieciowy i klikniesz na katalogu wynikowym tuż pod nim, a będziesz mógł przeglądać źródła tego projektu (rysunek 1).

Rysunek 1. Przegląd projektu procesora sieciowego we wtyczce nRFConnect

Po wgraniu projektu przebieg procesu włączania i rozpoczynania rozgłaszania możemy sprawdzić na logach w konsoli (listing 9).

[00:00:03.084,899] <dbg> bt_control: bt_control_init: BLE init...
[00:00:03.086,303] <inf> main: Tick 0
[00:00:03.097,869] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
[00:00:03.097,900] <inf> bt_hci_core: HW Variant: nRF53x (0x0003)
[00:00:03.097,900] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 54.58864 Build 1214809870
[00:00:03.099,426] <inf> bt_hci_core: Identity: F5:20:E7:1F:38:EF (random)
[00:00:03.099,456] <inf> bt_hci_core: HCI: version 5.4 (0x0d) revision 0x218f, manufacturer 0x0059
[00:00:03.099,487] <inf> bt_hci_core: LMP: version 5.4 (0x0d) subver 0x218f
[00:00:06.844,604] <dbg> bt_control: bt_ready_cb: BLE MAC: F5:20:E7:1F:38:EF (random)
[00:00:06.846,313] <dbg> bt_control: bt_ready_cb: BLE advertisement started

Listing 9. Log procesu włączania i rozpoczynanie rozgłaszania

Zauważ wpisy w logu, których nie wprowadzaliśmy – te oznaczone jako bt_hci_core. Są to informacje ze stosu BLE Zephyra.

Teraz możesz odnaleźć urządzenie „LED control” w standardowym menu Bluetooth telefonu.

My posłużymy się jednak bardziej zaawansowanym narzędziem.

Aplikacja nRF Connect

Zainstaluj „nRF Connect for Mobile” – jest dostępna zarówno na smartfony z Androidem, jak i z iOS.

Po uruchomieniu rozpocznij skanowanie (w prawym górnym rogu). Nasza płytka powinna się pojawić pośród innych urządzeń BT (rysunek 2).

Rysunek 2. Ekran aplikacji nRF Connect – wyszukiwanie płytki

Po kliknięciu w CONNECT powinniśmy zobaczyć serwisy udostępniane przez nasze urządzenie. Będą to tylko podstawowe dane, potrzebne np. do pobrania nazwy naszego urządzenia (rysunek 3).

Rysunek 3. Podstawowe serwisy

Czas zatem dodać własny serwis.

Serwis LED

Cały serwis obsługujący LED mieści się w pliku src/bt_led_svc.c (listing 10).

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include "bt_led_svc.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_led_svc, LOG_LEVEL_DBG);

// UUID for LED Service
#define BT_UUID_LED_SERVICE BT_UUID_DECLARE_128 \
(BT_UUID_128_ENCODE(0x7c8482f0, 0x1104, 0x44b8, 0xa354, 0x3f5b8488083d))
// UUID for LED Characteristics
#define BT_UUID_LED_STATE_CHR BT_UUID_DECLARE_128 \
(BT_UUID_128_ENCODE(0x7c8482f1, 0x1104, 0x44b8, 0xa354, 0x3f5b8488083d))
#define BT_UUID_LED_BLINK_CHR BT_UUID_DECLARE_128 \
(BT_UUID_128_ENCODE(0x7c8482f2, 0x1104, 0x44b8, 0xa354, 0x3f5b8488083d))

// Callback function pointers for BLE write operations
static led_cb_t write_state_cb = NULL;
static led_cb_t write_blink_cb = NULL;

static ssize_t write_chr_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len,
uint16_t offset, uint8_t flags) {

LOG_HEXDUMP_INF(buf, len, "Received buffer:");

if (len != sizeof(uint32_t)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}

unsigned int received = *(unsigned int*)buf;

if (bt_uuid_cmp(attr->uuid,BT_UUID_LED_STATE_CHR) == 0) {
LOG_INF("Led state update: %u", received);
if (write_state_cb != NULL) {
write_state_cb(received);
}
}

if (bt_uuid_cmp(attr->uuid,BT_UUID_LED_BLINK_CHR) == 0) {
LOG_INF("Led blink update: %u", received);
if (write_blink_cb != NULL) {
write_blink_cb(received);
}
}

return len;
}

// Define the GATT service and characteristics
BT_GATT_SERVICE_DEFINE(led_service,
BT_GATT_PRIMARY_SERVICE(BT_UUID_LED_SERVICE),

BT_GATT_CHARACTERISTIC(BT_UUID_LED_STATE_CHR,
BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE,
NULL, write_chr_cb, NULL),

BT_GATT_CHARACTERISTIC(BT_UUID_LED_BLINK_CHR,
BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE,
NULL, write_chr_cb, NULL)
);

void bt_led_svc_set_callbacks(led_cb_t write_state,
led_cb_t write_blink) {
write_state_cb = write_state;
write_blink_cb = write_blink;
}

Listing 10. Plik src/bt_led_svc.c – serwis BLE

Towarzyszący mu plik nagłówkowy src/bt_led_svc.h pozwala na spięcie obu charakterystyk serwisu z wcześniej napisanym kodem (listing 11).

#pragma once

#include <zephyr/types.h>

typedef void (*led_cb_t)(unsigned int param);
void bt_led_svc_set_callbacks(led_cb_t write_state, led_cb_t write_blink);

Listing 11. Plik src/bt_led_svc.h – serwis BLE

Definicja serwisu odbywa się poprzez makro BT_GATT_SERVICE_DEFINE na końcu pliku.

To makro tworzy serwis z identyfikatorem BT_UUID_LED_SERVICE oraz dwie charakterystyki: jedną do zapisu stanu LED-a i drugą – do zapisu okresu, z jakim ma migać.

UUID (Universally Unique Identifier) to unikalny identyfikator, który w Bluetooth Low Energy służy do rozpoznawania serwisów i charakterystyk. Każdy serwis i charakterystyka musi mieć przypisany UUID, który umożliwia identyfikację w trakcie komunikacji między urządzeniami. UUID mają długość 128 bitów, jednak w przypadku oficjalnie rozpoznawanych identyfikatorów korzystamy ze skróconych, 16-bitowych odpowiedników. Dla niestandardowych serwisów UUID można wygenerować, np. korzystając z narzędzi online, takich jak [1]. Jednak wygodnie jest, jeśli pierwsza część UUID serwisu kończy się zerem, a charakterystyki mają te wartości kolejno zwiększane o jeden.

Konfiguracja naszych dwóch charakterystyk odbywa się poprzez kolejne parametry makra BT_GATT_CHARACTERISTIC:

  • UUID: kolejno zwiększane o 1,
  • typ: BT_GATT_CHRC_WRITE (charakterystyka do zapisu),
  • uprawnienia: BT_GATT_PERM_WRITE (zezwolenie na zapis),
  • callback odczytu: brak (więc podano NULL),
  • callback zapisu: write_chr_cb,
  • dodatkowe dane: brak (więc NULL).

W tym przykładzie funkcje, które będą wywołane przy zapisie do charakterystyki poprzez smartfon (callback), to jedna i ta sama funkcja – write_chr_cb, a rozróżnienie następuje w jej środku poprzez porównanie UUID. Po wpisaniu parametru w charakterystyce BT_UUID_LED_STATE_CHR zostanie też uruchomiona funkcja skojarzona z write_state_cb, natomiast jeśli parametr zostanie wpisany do charakterystyki BT_UUID_LED_BLINK_CHR, przekażemy go do write_blink_cb.

Oczywiście należy jeszcze pamiętać, aby dodać plik src/bt_led_svc.c do systemu budowania w pliku CMakeLists.txt.

Firmware w tym stanie będzie już zgłaszał naszą charakterystykę. Będziemy nawet w logu widzieć wysłane ze smartfona dane. Aby faktycznie zmienić stan LED, musimy nieco dodać do pliku src/main.c.

void led_state_set(unsigned int state) {
led_send((led_msg){ LED_STATE, state});
}
void led_blink_set(unsigned int param) {
led_send((led_msg){ LED_BLINK, param});
}
Listing 12. Nowe funkcje w main.c – funkcje przekazujące parametr do sterowania LED

Dodajemy funkcje przekazujące parametr do modułu led_control w pliku main.c (listing 12) i przekazujemy te funkcje serwisowi w funkcji main tuż po włączeniu BLE (listing 13). Oczywiście trzeba też dołączyć bt_led_svc.h w pliku main.c

  bt_control_init();
bt_led_svc_set_callbacks(led_state_set,led_blink_set);

Listing 13. Włączenie BLE i inicjacja serwisu LED

Od teraz, korzystając z aplikacji mobilnej, możemy do naszej charakterystyki o UUID 7c8482f2-1104-44b8-a354-3f5b8488083d wpisać nowy okres migania LED w milisekundach (rysunek 4), pamiętając o wybraniu typu zmiennej jako UINT 32 (Little Endian). Nowa wartość zostanie natychmiast przesłana do naszej płytki, gdzie możemy w czasie rzeczywistym obserwować jej wpływ na zachowanie diody. Przykładowo, po wpisaniu 500, zobaczymy, że nasz LED co sekundę zaświeca się i gaśnie.

Rysunek 4. Wpisywanie parametru LED

Podsumowanie

W tej części kursu rozszerzyliśmy kod sterujący LED za pomocą przycisków i konsoli o możliwość sterowania diodą LED poprzez Bluetooth Low Energy (BLE). Dzięki temu możemy teraz ustawiać stan diody LED bezprzewodowo, korzystając np. ze smartfona.

Krzysztof Kierys
Paweł Jachimowski

Odnośniki w tekście
[1] https://www.uuidgenerator.net/

Artykuł ukazał się w
Elektronika Praktyczna
październik 2024
DO POBRANIA
Materiały dodatkowe
Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik styczeń 2025

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio styczeń - luty 2025

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje listopad - grudzień 2024

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna styczeń 2025

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich styczeń 2025

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów