Sending data between a UART and a Bluetooth LE connection
This exercise will focus on Nordic UART Service (NUS). NUS is quite a popular and versatile custom GATT service. The service emulates a serial port over Bluetooth LE, which allows you to send any sort of data back and forth between a UART connection (or a virtual serial port emulated over USB) and a Bluetooth LE connection.
The service has two characteristics:
RX Characteristic (Write, Write w/o response): For sending data to the board
TX Characteristic (Notify): For receiving data from the board
When a Bluetooth LE connection is established between a peripheral and a central, NUS forwards any data received on the RX pin of the UART0 peripheral to the Bluetooth LE central as notifications through the TX Characteristic. Any data sent from the Bluetooth LE central through the RX Characteristic is sent out of the UART0 peripheral’s TX pin.
Remember that on Nordic DKs, the UART0 peripheral is typically gated through the SEGGER debugger/programmer chip (aka: interface MCU) to a USB CDC virtual serial port that you can connect directly to your PC.
Note
The code provided in this exercise uses NUS to forward/receive data to/from the UART0 peripheral. You could easily modify the exercise to use NUS with UART1 or other peripherals.
In this exercise, we will learn how to use NUS to exchange data over Bluetooth LE between your PC and your smartphone or tablet running nRF Connect for Mobile.
Enabling this Kconfig will make the build system include the nus.c and the nus.h files. Since nus.c already includes the static service declaration and its characteristics, including the source files will by itself add NUS to the attribute table of your application.
In the application code (main.c), we will mainly need to do the following tasks:
Initialize the UART peripheral.
Define and register an application callback function to forward the data received from a Bluetooth LE connection to UART.
Call a function to send data received from UART to a Bluetooth LE connection.
We will first spend some time examining the NUS service implementation.
2. Examine the NUS service declaration
This is declared in nus.c, (found in <install_path>\nrf\subsys\bluetooth\services\nus.c). We will not modify the source files of the NUS service.
The declaration statically creates and adds the service with the UUID BT_UUID_NUS_SERVICE (defined in nus.h). and its two characteristics, the RX Characteristic and TX Characteristic.
Notice the presence of the conditional compilation flag CONFIG_BT_NUS_AUTHEN. If enabled, it will do the following to the static configurations of the characteristics of the NUS service:
Change the characteristic access permission of the TX Characteristic from BT_GATT_PERM_READ to BT_GATT_PERM_READ_AUTHEN (requires authentication & encryption).
Change the characteristic access permission of the Client Characteristic Configuration Descriptor of the TX Characteristic from BT_GATT_PERM_READ | BT_GATT_PERM_WRITE to BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN (requires authentication & encryption).
Change the characteristic access permission of the RX Characteristic from BT_GATT_PERM_READ | BT_GATT_PERM_WRITE to BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN (requires authentication & encryption).
Authenticated connections and encryption will be the focus of the next lesson; therefore, we will disable the CONFIG_BT_NUS_AUTHEN flag in this lesson.
Also, notice that for the write callback of the RX Characteristic, we are registering the function on_receive(). This function is called every time the Bluetooth central device writes data to the RX Characteristic. We will dissect it in the next step.
3. Examine the write callback function of the RX Characteristic
Let’s examine the write callback function of the RX Characteristic on_receive() in nus.c . The function calls the application callback function and passes it three parameters. The connection handle (in case multiconnection is used), the data received over Bluetooth LE, and its size.
4. Examine the bt_nus_init() which is also defined in nus.c
This function and the structure bt_nus_cb (defined in nus.h) have a similar intent to the my_lbs_init() we saw in Exercise 1. The purpose of bt_nus_init() and the bt_nus_cb struct is to facilitate decoupling of the code responsible for actually reading/writing to the UART peripheral (application code, i.e main.c) from the Bluetooth LE connectivity code (nus.c). Code decoupling adds a bit of complexity, but it does make the code way easier to maintain and scale.
Later, we will call this function in main.c and pass it a pointer to our application callback functions. Notice that the bt_nus_init() function can register three application callback functions:
Data received callback (Mandatory). The data has been received as a write request on the RX Characteristic, so the application callback function must do the action needed to forward the data to the UART peripheral.
Data sent callback (Optional). This function pointer can allow you to register an application callback function to be triggered when data has been sent as a notification to the TX Characteristic. We will not use it in this exercise.
Send state callback (Optional). This function pointer can allow you to register an application callback function to be triggered when a remote Bluetooth LE device subscribes or unsubscribes to the TX Characteristic notifications. We will not use it in this exercise.
5. Examine the function responsible for sending notifications
The last function we will examine in nus.c , is the function responsible for sending notifications over a Bluetooth LE connection bt_nus_send(). We will call bt_nus_send() from the application code (main.c) to forward the data received from the UART to a remote device over a Bluetooth LE connection.
Unlike in LBS, where we supported a single Bluetooth LE connection, NUS can support simultaneous connections. Therefore, the implementation of sending notifications is slightly different.
We will check the connection parameter conn (of type struct bt_conn) if it equals NULL, we will send notifications to all connected devices. On the other hand, if a specific connection is passed to the bt_nus_send() function, we will manually check if notification is enabled by the client on that connection and then send notification to that particular client.
With this, we have a good understanding of the implementation of Nordic UART Service. In the next steps, we will cover how to use it in an application code.
6. Declare two FIFO data structures
Declare two FIFOs data structures and the FIFO data item to hold the following:
The data received from the UART peripheral, and we want to send over a Bluetooth LE connection (fifo_uart_rx_data).
The data was received from a Bluetooth LE connection, and we want to send over UART (fifo_uart_tx_data).
Notice that the 1st word is reserved for use by FIFO as required by the FIFO implementation. The second member of the structure, which will hold the actual data, is an array of bytes of size CONFIG_BT_NUS_UART_BUFFER_SIZE. It is user configurable. The default size is 40 bytes.
7. Initialize the UART peripheral
Setting up the UART peripheral driver, using its asynchronous API, and assigning an application callback function is covered thoroughly in the nRF Connect SDK Fundementals course – Lesson 5. Feel free to revisit the Lesson in the nRF Connect SDK Fundamentals course to refresh the information if needed.
Add the call to uart_init() in main() as shown below
Copy
err = uart_init();if (err) {error(); }
C
8. Forward the data received from a Bluetooth LE connection to the UART peripheral.
8.1 Create a variable of type bt_nus_cb and initialize it.
This variable will hold the application callback functions for the NUS service.
We will set the data received callback to bt_receive_cb, which is covered in a following step.
8.2 Pass your application callback functions stored in nus_cb to the NUS service by calling bt_nus_init()
Add the following code in main()
Copy
err = bt_nus_init(&nus_cb);if (err) {LOG_ERR("Failed to initialize UART service (err: %d)", err);return0; }
C
8.3 The bt_receive_cb() function will be called by NUS when data has been received as a write request on the RX Characteristic. The data received from a Bluetooth LE connection will be available through the pointer data with the length len . We will call the UART peripheral function uart_tx to forward the data received over Bluetooth LE to the UART peripheral.
Add the following code inside bt_receive_cb() function.
In case there is an ongoing transfer already or an error, we will put the data in the fifo_uart_tx_data and try to send it again inside the uart_cb UART callback.
9. Receiving data from the UART peripheral and sending it to a Bluetooth LE connection.
9.1 Push the data received from the UART peripheral into the fifo_uart_rx_data FIFO.
On the UART_RX_BUF_RELEASED event in the uart_cb callback function of the UART peripheral, we will put the data received from UART into the FIFO by calling k_fifo_put(). The UART_RX_BUF_RELEASED event is triggered when the buffer is no longer used by UART driver.
Add the following line inside the UART callback function in the UART_RX_BUF_RELEASED event.
Copy
k_fifo_put(&fifo_uart_rx_data, buf);
C
9.2 Create a dedicated thread for sending the data over Bluetooth LE.
We will create a thread and associate it with the function ble_write_thread() which we will develop in the next step. The thread is assigned the stack STACKSIZE (1024 by default), and the priority PRIORITY (7 by default).
Note that the thread will be automatically scheduled for execution by the scheduler.
9.3 Define the thread function
Copy
voidble_write_thread(void){ /* Don't go any further until BLE is initialized */k_sem_take(&ble_init_ok, K_FOREVER);structuart_data_t nus_data = { .len = 0, };for (;;) { /* Wait indefinitely for data to be sent over bluetooth */structuart_data_t *buf = k_fifo_get(&fifo_uart_rx_data, K_FOREVER);int plen = MIN(sizeof(nus_data.data) - nus_data.len, buf->len);int loc = 0;while (plen > 0) {memcpy(&nus_data.data[nus_data.len], &buf->data[loc], plen);nus_data.len += plen; loc += plen;if (nus_data.len >= sizeof(nus_data.data) || (nus_data.data[nus_data.len - 1] == '\n') || (nus_data.data[nus_data.len - 1] == '\r')) {if (bt_nus_send(NULL, nus_data.data, nus_data.len)) {LOG_WRN("Failed to send data over BLE connection"); }nus_data.len = 0; } plen = MIN(sizeof(nus_data.data), buf->len - loc); }k_free(buf); }}
C
In this thread, we have an infinite loop where we call k_fifo_get() to get the data from the FIFO. We will send the data to connected Bluetooth LE device(s) as notification by calling the NUS function bt_nus_send().
Notice that we passed K_FOREVER as the second parameter for k_fifo_get(). This means the thread will be scheduled out if there is no data in the FIFO. Once the UART peripheral callback function uart_cb puts data in the FIFO, the thread will be scheduled back for execution.
Also, notice that the thread will only start after the Bluetooth LE stack has been initialized through the use of the semaphore: k_sem_take(&ble_init_ok, K_FOREVER).
10. (Only for nRF54L15 DK) Set ZMS as the storage backend.
Copy
CONFIG_ZMS=y
10. Build and flash the application on your board.
You should notice that LED1 on your board is blinking now, indicating that your board is advertising.
Testing
11. Open a terminal to view the log output from the application
Just like we have done in previous exercises, connect to the COM port of your DK in VS Code by expanding your device under Connected Devices and selecting the COM port for the device. Note on some development kits; there might be more than one COM port. Use the one that you see the Starting Nordic UART service example log on.
Your log should look like below:
*** Booting nRF Connect SDK ***Starting Nordic UART service example
Terminal
If you don’t see that log on the terminal, since it only prints on bootup once, you can press the reset button on the board to see it.
12. Connect to your device via your smartphone.
Open nRF Connect for Mobile on your smartphone. In the Scanner tab, locate the device, now named “Nordic_UART_Service” and connect to it, as done in the previous exercises.
13. Send data from your phone to the board.
In nRF Connect for Mobile, press on the arrow next to the RX Characteristic. You will be prompted with a small window.
Type a message in the Write value box, select the type of operation: Request (write with response) or Command (write with no response), and press SEND.
nRF Connect for Android
Select UTF8 to send a string. Type a message in the box, select the type of operation: Request (write with response) or Command (write with no response), and press Write.
nRF Connect for iOS
The message will be forwarded to the UART peripheral.
*** Booting nRF Connect SDK ***Starting Nordic UART service exampleHello from phone!
Terminal
14. Send data from your PC to your phone through the board.
14.1 In nRF Connect for mobile, subscribe to the TX Characteristic by pressing on the icon next to the TX Characteristic
14.2 In nRF Terminal, type a message to send to the remote device (for example Hello from PC!) and hit enter (to send an end-of-line and carriage return).
Important
Please note that the data you type will not be visible in the terminal since most terminals are in char mode by default. Once you hit enter on your keyboard, you will be able to see the data on the Smartphone/Tablet side using nRF Connect for Mobile.
The message will be forwarded from the UART peripheral through the Bluetooth LE connection to the central device running nRF Connect for Mobile and show up there.
The default Maximum Transmission Unit (MTU) set in the nRF Connect SDK Bluetooth stack is 23 bytes. It means you can’t send more data than can fit in the ATT MTU in one notification push. If you want to send more data in a single go, you need to increase this value; longer ATT payloads can be achieved, increasing the ATT throughput. This was covered in Lesson 3 – Exercise 2. Also, more details are available here
v2.9.0 – v2.7.0
Sending data between a UART and a Bluetooth LE connection
This exercise will focus on Nordic UART Service (NUS). NUS is quite a popular and versatile custom GATT service. The service emulates a serial port over Bluetooth LE, which allows you to send any sort of data back and forth between a UART connection (or a virtual serial port emulated over USB) and a Bluetooth LE connection.
The service has two characteristics:
RX Characteristic (Write, Write w/o response): For sending data to the board
TX Characteristic (Notify): For receiving data from the board
When a Bluetooth LE connection is established between a peripheral and a central, NUS forwards any data received on the RX pin of the UART0 peripheral to the Bluetooth LE central as notifications through the TX Characteristic. Any data sent from the Bluetooth LE central through the RX Characteristic is sent out of the UART0 peripheral’s TX pin.
Remember that on Nordic DKs, the UART0 peripheral is typically gated through the SEGGER debugger/programmer chip (aka: interface MCU) to a USB CDC virtual serial port that you can connect directly to your PC.
Note
The code provided in this exercise uses NUS to forward/receive data to/from the UART0 peripheral. You could easily modify the exercise to use NUS with UART1 or other peripherals.
In this exercise, we will learn how to use NUS to exchange data over Bluetooth LE between your PC and your smartphone or tablet running nRF Connect for Mobile.
Enabling this Kconfig will make the build system include the nus.c and the nus.h files. Since nus.c already includes the static service declaration and its characteristics, including the source files will by itself add NUS to the attribute table of your application.
In the application code (main.c), we will mainly need to do the following tasks:
Initialize the UART peripheral.
Define and register an application callback function to forward the data received from a Bluetooth LE connection to UART.
Call a function to send data received from UART to a Bluetooth LE connection.
We will first spend some time examining the NUS service implementation.
2. Examine the NUS service declaration
This is declared in nus.c, (found in <install_path>\nrf\subsys\bluetooth\services\nus.c). We will not modify the source files of the NUS service.
The declaration statically creates and adds the service with the UUID BT_UUID_NUS_SERVICE (defined in nus.h). and its two characteristics, the RX Characteristic and TX Characteristic.
Notice the presence of the conditional compilation flag CONFIG_BT_NUS_AUTHEN. If enabled, it will do the following to the static configurations of the characteristics of the NUS service:
Change the characteristic access permission of the TX Characteristic from BT_GATT_PERM_READ to BT_GATT_PERM_READ_AUTHEN (requires authentication & encryption).
Change the characteristic access permission of the Client Characteristic Configuration Descriptor of the TX Characteristic from BT_GATT_PERM_READ | BT_GATT_PERM_WRITE to BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN (requires authentication & encryption).
Change the characteristic access permission of the RX Characteristic from BT_GATT_PERM_READ | BT_GATT_PERM_WRITE to BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN (requires authentication & encryption).
Authenticated connections and encryption will be the focus of the next lesson; therefore, we will disable the CONFIG_BT_NUS_AUTHEN flag in this lesson.
Also, notice that for the write callback of the RX Characteristic, we are registering the function on_receive(). This function is called every time the Bluetooth central device writes data to the RX Characteristic. We will dissect it in the next step.
3. Examine the write callback function of the RX Characteristic
Let’s examine the write callback function of the RX Characteristic on_receive() in nus.c . The function calls the application callback function and passes it three parameters. The connection handle (in case multiconnection is used), the data received over Bluetooth LE, and its size.
4. Examine the bt_nus_init() which is also defined in nus.c
This function and the structure bt_nus_cb (defined in nus.h) have a similar intent to the my_lbs_init() we saw in Exercise 1. The purpose of bt_nus_init() and the bt_nus_cb struct is to facilitate decoupling of the code responsible for actually reading/writing to the UART peripheral (application code, i.e main.c) from the Bluetooth LE connectivity code (nus.c). Code decoupling adds a bit of complexity, but it does make the code way easier to maintain and scale.
Later, we will call this function in main.c and pass it a pointer to our application callback functions. Notice that the bt_nus_init() function can register three application callback functions:
Data received callback (Mandatory). The data has been received as a write request on the RX Characteristic, so the application callback function must do the action needed to forward the data to the UART peripheral.
Data sent callback (Optional). This function pointer can allow you to register an application callback function to be triggered when data has been sent as a notification to the TX Characteristic. We will not use it in this exercise.
Send state callback (Optional). This function pointer can allow you to register an application callback function to be triggered when a remote Bluetooth LE device subscribes or unsubscribes to the TX Characteristic notifications. We will not use it in this exercise.
5. Examine the function responsible for sending notifications
The last function we will examine in nus.c , is the function responsible for sending notifications over a Bluetooth LE connection bt_nus_send(). We will call bt_nus_send() from the application code (main.c) to forward the data received from the UART to a remote device over a Bluetooth LE connection.
Unlike in LBS, where we supported a single Bluetooth LE connection, NUS can support simultaneous connections. Therefore, the implementation of sending notifications is slightly different.
We will check the connection parameter conn (of type struct bt_conn) if it equals NULL, we will send notifications to all connected devices. On the other hand, if a specific connection is passed to the bt_nus_send() function, we will manually check if notification is enabled by the client on that connection and then send notification to that particular client.
With this, we have a good understanding of the implementation of Nordic UART Service. In the next steps, we will cover how to use it in an application code.
6. Declare two FIFO data structures
Declare two FIFOs data structures and the FIFO data item to hold the following:
The data received from the UART peripheral, and we want to send over a Bluetooth LE connection (fifo_uart_rx_data).
The data was received from a Bluetooth LE connection, and we want to send over UART (fifo_uart_tx_data).
Notice that the 1st word is reserved for use by FIFO as required by the FIFO implementation. The second member of the structure, which will hold the actual data, is an array of bytes of size CONFIG_BT_NUS_UART_BUFFER_SIZE. It is user configurable. The default size is 40 bytes.
7. Initialize the UART peripheral
Setting up the UART peripheral driver, using its asynchronous API, and assigning an application callback function is covered thoroughly in the nRF Connect SDK Fundementals course – Lesson 5. Feel free to revisit the Lesson in the nRF Connect SDK Fundamentals course to refresh the information if needed.
Add the call to uart_init() in main() as shown below
Copy
err = uart_init();if (err) {error(); }
C
8. Forward the data received from a Bluetooth LE connection to the UART peripheral.
8.1 Create a variable of type bt_nus_cb and initialize it.
This variable will hold the application callback functions for the NUS service.
We will set the data received callback to bt_receive_cb, which is covered in a following step.
8.2 Pass your application callback functions stored in nus_cb to the NUS service by calling bt_nus_init()
Add the following code in main()
Copy
err = bt_nus_init(&nus_cb);if (err) {LOG_ERR("Failed to initialize UART service (err: %d)", err);return0; }
C
8.3 The bt_receive_cb() function will be called by NUS when data has been received as a write request on the RX Characteristic. The data received from a Bluetooth LE connection will be available through the pointer data with the length len . We will call the UART peripheral function uart_tx to forward the data received over Bluetooth LE to the UART peripheral.
Add the following code inside bt_receive_cb() function.
In case there is an ongoing transfer already or an error, we will put the data in the fifo_uart_tx_data and try to send it again inside the uart_cb UART callback.
9. Receiving data from the UART peripheral and sending it to a Bluetooth LE connection.
9.1 Push the data received from the UART peripheral into the fifo_uart_rx_data FIFO.
On the UART_RX_BUF_RELEASED event in the uart_cb callback function of the UART peripheral, we will put the data received from UART into the FIFO by calling k_fifo_put(). The UART_RX_BUF_RELEASED event is triggered when the buffer is no longer used by UART driver.
Add the following line inside the UART callback function in the UART_RX_BUF_RELEASED event.
Copy
k_fifo_put(&fifo_uart_rx_data, buf);
C
9.2 Create a dedicated thread for sending the data over Bluetooth LE.
We will create a thread and associate it with the function ble_write_thread() which we will develop in the next step. The thread is assigned the stack STACKSIZE (1024 by default), and the priority PRIORITY (7 by default).
Note that the thread will be automatically scheduled for execution by the scheduler.
9.3 Define the thread function
Copy
voidble_write_thread(void){ /* Don't go any further until BLE is initialized */k_sem_take(&ble_init_ok, K_FOREVER);structuart_data_t nus_data = { .len = 0, };for (;;) { /* Wait indefinitely for data to be sent over bluetooth */structuart_data_t *buf = k_fifo_get(&fifo_uart_rx_data, K_FOREVER);int plen = MIN(sizeof(nus_data.data) - nus_data.len, buf->len);int loc = 0;while (plen > 0) {memcpy(&nus_data.data[nus_data.len], &buf->data[loc], plen);nus_data.len += plen; loc += plen;if (nus_data.len >= sizeof(nus_data.data) || (nus_data.data[nus_data.len - 1] == '\n') || (nus_data.data[nus_data.len - 1] == '\r')) {if (bt_nus_send(NULL, nus_data.data, nus_data.len)) {LOG_WRN("Failed to send data over BLE connection"); }nus_data.len = 0; } plen = MIN(sizeof(nus_data.data), buf->len - loc); }k_free(buf); }}
C
In this thread, we have an infinite loop where we call k_fifo_get() to get the data from the FIFO. We will send the data to connected Bluetooth LE device(s) as notification by calling the NUS function bt_nus_send().
Notice that we passed K_FOREVER as the second parameter for k_fifo_get(). This means the thread will be scheduled out if there is no data in the FIFO. Once the UART peripheral callback function uart_cb puts data in the FIFO, the thread will be scheduled back for execution.
Also, notice that the thread will only start after the Bluetooth LE stack has been initialized through the use of the semaphore: k_sem_take(&ble_init_ok, K_FOREVER).
10. (Only for nRF54L15 DK) Set ZMS as the storage backend.
Copy
CONFIG_ZMS=y
10. Build and flash the application on your board.
You should notice that LED1 on your board is blinking now, indicating that your board is advertising.
Testing
11. Open a terminal to view the log output from the application
Just like we have done in previous exercises, connect to the COM port of your DK in VS Code by expanding your device under Connected Devices and selecting the COM port for the device. Note on some development kits; there might be more than one COM port. Use the one that you see the Starting Nordic UART service example log on.
Your log should look like below:
*** Booting nRF Connect SDK ***Starting Nordic UART service example
Terminal
If you don’t see that log on the terminal, since it only prints on bootup once, you can press the reset button on the board to see it.
12. Connect to your device via your smartphone.
Open nRF Connect for Mobile on your smartphone. In the Scanner tab, locate the device, now named “Nordic_UART_Service” and connect to it, as done in the previous exercises.
13. Send data from your phone to the board.
In nRF Connect for Mobile, press on the arrow next to the RX Characteristic. You will be prompted with a small window.
Type a message in the Write value box, select the type of operation: Request (write with response) or Command (write with no response), and press SEND.
nRF Connect for Android
Select UTF8 to send a string. Type a message in the box, select the type of operation: Request (write with response) or Command (write with no response), and press Write.
nRF Connect for iOS
The message will be forwarded to the UART peripheral.
*** Booting nRF Connect SDK ***Starting Nordic UART service exampleHello from phone!
Terminal
14. Send data from your PC to your phone through the board.
14.1 In nRF Connect for mobile, subscribe to the TX Characteristic by pressing on the icon next to the TX Characteristic
14.2 In nRF Terminal, type a message to send to the remote device (for example Hello from PC!) and hit enter (to send an end-of-line and carriage return).
Important
Please note that the data you type will not be visible in the terminal since most terminals are in char mode by default. Once you hit enter on your keyboard, you will be able to see the data on the Smartphone/Tablet side using nRF Connect for Mobile.
The message will be forwarded from the UART peripheral through the Bluetooth LE connection to the central device running nRF Connect for Mobile and show up there.
The default Maximum Transmission Unit (MTU) set in the nRF Connect SDK Bluetooth stack is 23 bytes. It means you can’t send more data than can fit in the ATT MTU in one notification push. If you want to send more data in a single go, you need to increase this value; longer ATT payloads can be achieved, increasing the ATT throughput. This was covered in Lesson 3 – Exercise 2. Also, more details are available here
v2.6.2 – v2.3.0
Sending data between a UART and a Bluetooth LE connection
This exercise will focus on Nordic UART Service (NUS). NUS is quite a popular and versatile custom GATT service. The service emulates a serial port over Bluetooth LE, which allows you to send any sort of data back and forth between a UART connection (or a virtual serial port emulated over USB) and a Bluetooth LE connection.
The service has two characteristics:
RX Characteristic (Write, Write w/o response): For sending data to the board
TX Characteristic (Notify): For receiving data from the board
When a Bluetooth LE connection is established between a peripheral and a central, NUS forwards any data received on the RX pin of the UART0 peripheral to the Bluetooth LE central as notifications through the TX Characteristic. Any data sent from the Bluetooth LE central through the RX Characteristic is sent out of the UART0 peripheral’s TX pin.
Remember that on Nordic DKs, the UART0 peripheral is typically gated through the SEGGER debugger/programmer chip (aka: interface MCU) to a USB CDC virtual serial port that you can connect directly to your PC.
Note
The code provided in this exercise uses NUS to forward/receive data to/from the UART0 peripheral. You could easily modify the exercise to use NUS with UART1 or other peripherals.
In this exercise, we will learn how to use NUS to exchange data over Bluetooth LE between your PC and your smartphone or tablet running nRF Connect for Mobile.
Enabling this Kconfig will make the build system include the nus.c and the nus.h files. Since nus.c already includes the static service declaration and its characteristics, including the source files will by itself add NUS to the attribute table of your application.
In the application code (main.c), we will mainly need to do the following tasks:
Initialize the UART peripheral.
Define and register an application callback function to forward the data received from a Bluetooth LE connection to UART.
Call a function to send data received from UART to a Bluetooth LE connection.
We will first spend some time examining the NUS service implementation.
2. Examine the NUS service declaration
This is declared in nus.c, (found in <install_path>\nrf\subsys\bluetooth\services\nus.c). We will not modify the source files of the NUS service.
The declaration statically creates and adds the service with the UUID BT_UUID_NUS_SERVICE (defined in nus.h). and its two characteristics, the RX Characteristic and TX Characteristic.
Notice the presence of the conditional compilation flag CONFIG_BT_NUS_AUTHEN. If enabled, it will do the following to the static configurations of the characteristics of the NUS service:
Change the characteristic access permission of the TX Characteristic from BT_GATT_PERM_READ to BT_GATT_PERM_READ_AUTHEN (requires authentication & encryption).
Change the characteristic access permission of the Client Characteristic Configuration Descriptor of the TX Characteristic from BT_GATT_PERM_READ | BT_GATT_PERM_WRITE to BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN (requires authentication & encryption).
Change the characteristic access permission of the RX Characteristic from BT_GATT_PERM_READ | BT_GATT_PERM_WRITE to BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN (requires authentication & encryption).
Authenticated connections and encryption will be the focus of the next lesson; therefore, we will disable the CONFIG_BT_NUS_AUTHEN flag in this lesson.
Also, notice that for the write callback of the RX Characteristic, we are registering the function on_receive(). This function is called every time the Bluetooth central device writes data to the RX Characteristic. We will dissect it in the next step.
3. Examine the write callback function of the RX Characteristic
Let’s examine the write callback function of the RX Characteristic on_receive() in nus.c . The function calls the application callback function and passes it three parameters. The connection handle (in case multiconnection is used), the data received over Bluetooth LE, and its size.
4. Examine the bt_nus_init() which is also defined in nus.c
This function and the structure bt_nus_cb (defined in nus.h) have a similar intent to the my_lbs_init() we saw in Exercise 1. The purpose of bt_nus_init() and the bt_nus_cb struct is to facilitate decoupling of the code responsible for actually reading/writing to the UART peripheral (application code, i.e main.c) from the Bluetooth LE connectivity code (nus.c). Code decoupling adds a bit of complexity, but it does make the code way easier to maintain and scale.
Later, we will call this function in main.c and pass it a pointer to our application callback functions. Notice that the bt_nus_init() function can register three application callback functions:
Data received callback (Mandatory). The data has been received as a write request on the RX Characteristic, so the application callback function must do the action needed to forward the data to the UART peripheral.
Data sent callback (Optional). This function pointer can allow you to register an application callback function to be triggered when data has been sent as a notification to the TX Characteristic. We will not use it in this exercise.
Send state callback (Optional). This function pointer can allow you to register an application callback function to be triggered when a remote Bluetooth LE device subscribes or unsubscribes to the TX Characteristic notifications. We will not use it in this exercise.
5. Examine the function responsible for sending notifications
The last function we will examine in nus.c , is the function responsible for sending notifications over a Bluetooth LE connection bt_nus_send(). We will call bt_nus_send() from the application code (main.c) to forward the data received from the UART to a remote device over a Bluetooth LE connection.
Unlike in LBS, where we supported a single Bluetooth LE connection, NUS can support simultaneous connections. Therefore, the implementation of sending notifications is slightly different.
We will check the connection parameter conn (of type struct bt_conn) if it equals NULL, we will send notifications to all connected devices. On the other hand, if a specific connection is passed to the bt_nus_send() function, we will manually check if notification is enabled by the client on that connection and then send notification to that particular client.
With this, we have a good understanding of the implementation of Nordic UART Service. In the next steps, we will cover how to use it in an application code.
6. Declare two FIFO data structures
Declare two FIFOs data structures and the FIFO data item to hold the following:
The data received from the UART peripheral, and we want to send over a Bluetooth LE connection (fifo_uart_rx_data).
The data was received from a Bluetooth LE connection, and we want to send over UART (fifo_uart_tx_data).
Notice that the 1st word is reserved for use by FIFO as required by the FIFO implementation. The second member of the structure, which will hold the actual data, is an array of bytes of size CONFIG_BT_NUS_UART_BUFFER_SIZE. It is user configurable. The default size is 40 bytes.
7. Initialize the UART peripheral
Setting up the UART peripheral driver, using its asynchronous API, and assigning an application callback function is covered thoroughly in the nRF Connect SDK Fundementals course – Lesson 5. Feel free to revisit the Lesson in the nRF Connect SDK Fundamentals course to refresh the information if needed.
Add the call to uart_init() in main() as shown below
Copy
err = uart_init();if (err) {error(); }
C
8. Forward the data received from a Bluetooth LE connection to the UART peripheral.
8.1 Create a variable of type bt_nus_cb and initialize it.
This variable will hold the application callback functions for the NUS service.
We will set the data received callback to bt_receive_cb, which is covered in a following step.
8.2 Pass your application callback functions stored in nus_cb to the NUS service by calling bt_nus_init()
Add the following code in main()
Copy
err = bt_nus_init(&nus_cb);if (err) {LOG_ERR("Failed to initialize UART service (err: %d)", err);return -1; }
C
8.3 The bt_receive_cb() function will be called by NUS when data has been received as a write request on the RX Characteristic. The data received from a Bluetooth LE connection will be available through the pointer data with the length len . We will call the UART peripheral function uart_tx to forward the data received over Bluetooth LE to the UART peripheral.
Add the following code inside bt_receive_cb() function.
In case there is an ongoing transfer already or an error, we will put the data in the fifo_uart_tx_data and try to send it again inside the uart_cb UART callback.
9. Receiving data from the UART peripheral and sending it to a Bluetooth LE connection.
9.1 Push the data received from the UART peripheral into the fifo_uart_rx_data FIFO.
On the UART_RX_BUF_RELEASED event in the uart_cb callback function of the UART peripheral, we will put the data received from UART into the FIFO by calling k_fifo_put(). The UART_RX_BUF_RELEASED event is triggered when the buffer is no longer used by UART driver.
Add the following line inside the UART callback function in the UART_RX_BUF_RELEASED event.
Copy
k_fifo_put(&fifo_uart_rx_data, buf);
C
9.2 Create a dedicated thread for sending the data over Bluetooth LE.
We will create a thread and associate it with the function ble_write_thread() which we will develop in the next step. The thread is assigned the stack STACKSIZE (1024 by default), and the priority PRIORITY (7 by default).
Note that the thread will be automatically scheduled for execution by the scheduler.
9.3 Define the thread function
Copy
voidble_write_thread(void){ /* Don't go any further until BLE is initialized */k_sem_take(&ble_init_ok, K_FOREVER);for (;;) { /* Wait indefinitely for data from the UART peripheral */structuart_data_t *buf = k_fifo_get(&fifo_uart_rx_data, K_FOREVER); /* Send data over Bluetooth LE to remote device(s) */if (bt_nus_send(NULL, buf->data, buf->len)) {LOG_WRN("Failed to send data over BLE connection"); }k_free(buf); }}
C
In this thread, we have an infinite loop where we call k_fifo_get() to get the data from the FIFO. We will send the data to connected Bluetooth LE device(s) as notification by calling the NUS function bt_nus_send().
Notice that we passed K_FOREVER as the second parameter for k_fifo_get(). This means the thread will be scheduled out if there is no data in the FIFO. Once the UART peripheral callback function uart_cb puts data in the FIFO, the thread will be scheduled back for execution.
Also, notice that the thread will only start after the Bluetooth LE stack has been initialized through the use of the semaphore: k_sem_take(&ble_init_ok, K_FOREVER).
10. Build and flash the application on your board.
You should notice that LED1 on your board is blinking now, indicating that your board is advertising.
Testing
11. Open a terminal to view the log output from the application
Just like we have done in previous exercises, connect to the COM port of your DK in VS Code by expanding your device under Connected Devices and selecting the COM port for the device. Note on some development kits; there might be more than one COM port. Use the one that you see the Starting Nordic UART service example log on.
Your log should look like below:
*** Booting nRF Connect SDK ***Starting Nordic UART service example
Terminal
If you don’t see that log on the terminal, since it only prints on bootup once, you can press the reset button on the board to see it.
12. Connect to your device via your smartphone.
Open nRF Connect for Mobile on your smartphone. In the Scanner tab, locate the device, now named “Nordic_UART_Service” and connect to it, as done in the previous exercises.
13. Send data from your phone to the board.
In nRF Connect for Mobile, press on the arrow next to the RX Characteristic. You will be prompted with a small window.
Type a message in the Write value box, select the type of operation: Request (write with response) or Command (write with no response), and press SEND.
nRF Connect for Android
Select UTF8 to send a string. Type a message in the box, select the type of operation: Request (write with response) or Command (write with no response), and press Write.
nRF Connect for iOS
The message will be forwarded to the UART peripheral.
*** Booting nRF Connect SDK ***Starting Nordic UART service exampleHello from phone!
Terminal
14. Send data from your PC to your phone through the board.
14.1 In nRF Connect for mobile, subscribe to the TX Characteristic by pressing on the icon next to the TX Characteristic
14.2 In nRF Terminal, type a message to send to the remote device (for example Hello from PC!) and hit enter (to send an end-of-line and carriage return).
Important
Please note that the data you type will not be visible in the terminal since most terminals are in char mode by default. Once you hit enter on your keyboard, you will be able to see the data on the Smartphone/Tablet side using nRF Connect for Mobile.
The message will be forwarded from the UART peripheral through the Bluetooth LE connection to the central device running nRF Connect for Mobile and show up there.
The default Maximum Transmission Unit (MTU) set in the nRF Connect SDK Bluetooth stack is 23 bytes. It means you can’t send more data than can fit in the ATT MTU in one notification push. If you want to send more data in a single go, you need to increase this value; longer ATT payloads can be achieved, increasing the ATT throughput. This was covered in Lesson 3 – Exercise 2. Also, more details are available here
Nordic Developer Academy Privacy Policy
1. Introduction
In this Privacy Policy you will find information on Nordic Semiconductor ASA (“Nordic Semiconductor”) processes your personal data when you use the Nordic Developer Academy.
References to “we” and “us” in this document refers to Nordic Semiconductor.
2. Our processing of personal data when you use the Nordic Developer Academy
2.1 Nordic Developer Academy
Nordic Semiconductor processes personal data in order to provide you with the features and functionality of the Nordic Developer Academy. Creating a user account is optional, but required if you want to track you progress and view your completed courses and obtained certificates. If you choose to create a user account, we will process the following categories of personal data:
Email
Name
Password (encrypted)
Course progression (e.g. which course you have completely or partly completed)
Certificate information, which consists of name of completed course and the validity of the certificate
Course results
During your use of the Nordic Developer Academy, you may also be asked if you want to provide feedback. If you choose to respond to any such surveys, we will also process the personal data in your responses in that survey.
The legal basis for this processing is GDPR article 6 (1) b. The processing is necessary for Nordic Semiconductor to provide the Nordic Developer Academy under the Terms of Service.
2.2 Analytics
If you consent to analytics, Nordic Semiconductor will use Google Analytics to obtain statistics about how the Nordic Developer Academy is used. This includes collecting information on for example what pages are viewed, the duration of the visit, the way in which the pages are maneuvered, what links are clicked, technical information about your equipment. The information is used to learn how Nordic Developer Academy is used and how the user experience can be further developed.
2.2 Newsletter
You can consent to receive newsletters from Nordic from within the Nordic Developer Academy. How your personal data is processed when you sign up for our newsletters is described in the Nordic Semiconductor Privacy Policy.
3. Retention period
We will store your personal data for as long you use the Nordic Developer Academy. If our systems register that you have not used your account for 36 months, your account will be deleted.
4. Additional information
Additional information on how we process personal data can be found in the Nordic Semiconductor Privacy Policy and Cookie Policy.
Nordic Developer Academy Terms of Service
1. Introduction
These terms and conditions (“Terms of Use”) apply to the use of the Nordic Developer Academy, provided by Nordic Semiconductor ASA, org. nr. 966 011 726, a public limited liability company registered in Norway (“Nordic Semiconductor”).
Nordic Developer Academy allows the user to take technical courses related to Nordic Semiconductor products, software and services, and obtain a certificate certifying completion of these courses. By completing the registration process for the Nordic Developer Academy, you are agreeing to be bound by these Terms of Use.
These Terms of Use are applicable as long as you have a user account giving you access to Nordic Developer Academy.
2. Access to and use of Nordic Developer Academy
Upon acceptance of these Terms of Use you are granted a non-exclusive right of access to, and use of Nordic Developer Academy, as it is provided to you at any time. Nordic Semiconductor provides Nordic Developer Academy to you free of charge, subject to the provisions of these Terms of Use and the Nordic Developer Academy Privacy Policy.
To access select features of Nordic Developer Academy, you need to create a user account. You are solely responsible for the security associated with your user account, including always keeping your login details safe.
You will able to receive an electronic certificate from Nordic Developer Academy upon completion of courses. By issuing you such a certificate, Nordic Semiconductor certifies that you have completed the applicable course, but does not provide any further warrants or endorsements for any particular skills or professional qualifications.
Nordic Semiconductor will continuously develop Nordic Developer Academy with new features and functionality, but reserves the right to remove or alter any existing functions without notice.
3. Acceptable use
You undertake that you will use Nordic Developer Academy in accordance with applicable law and regulations, and in accordance with these Terms of Use. You must not modify, adapt, or hack Nordic Developer Academy or modify another website so as to falsely imply that it is associated with Nordic Developer Academy, Nordic Semiconductor, or any other Nordic Semiconductor product, software or service.
You agree not to reproduce, duplicate, copy, sell, resell or in any other way exploit any portion of Nordic Developer Academy, use of Nordic Developer Academy, or access to Nordic Developer Academy without the express written permission by Nordic Semiconductor. You must not upload, post, host, or transmit unsolicited email, SMS, or \”spam\” messages.
You are responsible for ensuring that the information you post and the content you share does not;
contain false, misleading or otherwise erroneous information
infringe someone else’s copyrights or other intellectual property rights
contain sensitive personal data or
contain information that might be received as offensive or insulting.
Such information may be removed without prior notice.
Nordic Semiconductor reserves the right to at any time determine whether a use of Nordic Developer Academy is in violation of its requirements for acceptable use.
Violation of the at any time applicable requirements for acceptable use may result in termination of your account. We will take reasonable steps to notify you and state the reason for termination in such cases.
4. Routines for planned maintenance
Certain types of maintenance may imply a stop or reduction in availability of Nordic Developer Academy. Nordic Semiconductor does not warrant any level of service availability but will provide its best effort to limit the impact of any planned maintenance on the availability of Nordic Developer Academy.
5. Intellectual property rights
Nordic Semiconductor retains all rights to all elements of Nordic Developer Academy. This includes, but is not limited to, the concept, design, trademarks, know-how, trade secrets, copyrights and all other intellectual property rights.
Nordic Semiconductor receives all rights to all content uploaded or created in Nordic Developer Academy. You do not receive any license or usage rights to Nordic Developer Academy beyond what is explicitly stated in this Agreement.
6. Liability and damages
Nothing within these Terms of Use is intended to limit your statutory data privacy rights as a data subject, as described in the Nordic Developer Academy Privacy Policy. You acknowledge that errors might occur from time to time and waive any right to claim for compensation as a result of errors in Nordic Developer Academy. When an error occurs, you shall notify Nordic Semiconductor of the error and provide a description of the error situation.
You agree to indemnify Nordic Semiconductor for any loss, including indirect loss, arising out of or in connection with your use of Nordic Developer Academy or violations of these Terms of Use. Nordic Semiconductor shall not be held liable for, and does not warrant that (i) Nordic Developer Academy will meet your specific requirements, (ii) Nordic Developer Academy will be uninterrupted, timely, secure, or error-free, (iii) the results that may be obtained from the use of Nordic Developer Academy will be accurate or reliable, (iv) the quality of any products, services, information, or other material purchased or obtained by you through Nordic Developer Academy will meet your expectations, or that (v) any errors in Nordic Developer Academy will be corrected.
You accept that this is a service provided to you without any payment and hence you accept that Nordic Semiconductor will not be held responsible, or liable, for any breaches of these Terms of Use or any loss connected to your use of Nordic Developer Academy. Unless otherwise follows from mandatory law, Nordic Semiconductor will not accept any such responsibility or liability.
7. Change of terms
Nordic Semiconductor may update and change the Terms of Use from time to time. Nordic Semiconductor will seek to notify you about significant changes before such changes come into force and give you a possibility to evaluate the effects of proposed changes. Continued use of Nordic Developer Academy after any such changes shall constitute your acceptance of such changes. You can review the current version of the Terms of Use at any time at https://academy.nordicsemi.com/terms-of-service/
8. Transfer of rights
Nordic Semiconductor is entitled to transfer its rights and obligation pursuant to these Terms of Use to a third party as part of a merger or acquisition process, or as a result of other organizational changes.
9. Third Party Services
To the extent Nordic Developer Academy facilitates access to services provided by a third party, you agree to comply with the terms governing such third party services. Nordic Semiconductor shall not be held liable for any errors, omissions, inaccuracies, etc. related to such third party services.
10. Dispute resolution
The Terms of Use and any other legally binding agreement between yourself and Nordic Semiconductor shall be subject to Norwegian law and Norwegian courts’ exclusive jurisdiction.