Introduction
Description | Bluetooth Low Energy | IEEE 802.15.4 | ANT | Proprietary |
---|---|---|---|---|
Frequency Modulation | FHSS | DSSS | Fixed frequency | FHSS or fixed frequency |
Supported topologies | Peer-to-peer, star | Peer-to-peer, star, mesh | Peer-to-peer, star, mesh | Peer-to-peer, star, mesh |
Security | 128-bit AES | 128-bit key, 128-bit AES | 64-bit key, 128-bit AES | 128-bit AES |
Data rate | 2 Mbps, 1 Mbps, 500 kbps, 125 kbps | 250 kbps | 1 Mbps | 2 Mbps, 1 Mbps, 500 kbps, 250 kbps, 125 kbps |
Number of connections | Up to 20 | Very high | Very high | Very high |
Effective throughput | Up to 1.4 Mbps | Up to 150 kbps | Up to 60 kbps | Up to 1000 kbps |
Source: https://infocenter.nordicsemi.com/topic/ug_getting_started/UG/gs/avail_protocols.html
One of the unique features of Nordic Semiconductor’s nRF5 Series of SoCs is that it supports multiple short range RF protocols operating in the 2.4 GHz bandwidth, such as Bluetooth, ANT, IEEE 802.15.4 (including Thread & Zigbee) and proprietary. Thus some of these protocols can be used to create your own Wireless Personal Area Networking (WPAN) application
The Nordic Semiconductor’s website also tells us that the nRF5 Series SoCs are capable of running multiple protocols at the same time, allowing you to communicate with a broad range of devices within the 2.4 GHz range. So there is plenty of scope to experiment beyond BLE.
As the PAN1780 Evaluation Kit (EVK) comes with 2 dev boards I thought to use this evaluation kit to help me learn by testing out these different RF options using the examples provided in the nRF5 SDK.
I thought to start with the IEEE 802.15.4 protocol in this blog.
Using IEEE 802.15.4 protocol
I know very little about the IEEE 802.15.4 protocol, so from my online research I've learnt that the standardised IEEE wireless 802.15.4 protocol covers both the PHY (physical) layer and MAC (medium access control) sublayers and is designed to handle encrypted (optional) low-power, low data rate communication (at a raw bit rate up to 250kbps) using direct-sequence spread spectrum (DSSS) modulation within a WPAN. The nRF52840 specification states that it uses the IEEE 802.15.4-2006 version while the latest version is IEEE 802.15.4-2020.
The nRF5 SDK provides an 802.15.4 MAC library which is for nRF52840 SoC’s only. This library can be used to build a proprietary network or to act as a basis for a variety of different higher layer standards including Thread, ZigBee, Wireless HART, RF4CE, ISA100.11a, and 6LoWPAN.
So in order to create a Zigbee or Thread protocol stack you would then need to add in the Network and Application sublayers that are specific to that communication / networking protocol. Note that Nordic offers a full protocol stack solution for Thread via OpenThread. There is a separate section on how to get started with Zigbee and Thread and full API details in the Nordic SDK documentation: https://infocenter.nordicsemi.com/topic/struct_sdk/struct/sdk_thread_zigbee_latest.html
I'm not going to get into that. Instead I am going to start with the basic Wireless UART example, which is available only for the nRF52840.
Wireless UART Example
The Wireless UART example simply transmits and receives data between two nodes configured as a point-point WPAN.
There are two versions of this example available. There is a raw option, which sends open/unsecure data, which is the example I will try out, and there is also a secure version where the RF communication is secured. Details about the example can be found here: https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.0.2/wireless_uart_15_4.html
Then within each version there are two code folders, named “first” and “second”, that need to be used. This designates a hard coded id for each device. So the code folder “first” designates one of the boards as ID “A” or 0x10 and the folder “second” would designate the other board as “B” or 0x11. The device addresses are configured within the sdk_config.h file.
// <h> Application /========================================================== // <o> CONFIG_DEVICE_SHORT_ADDRESS - Device short address #ifndef CONFIG_DEVICE_SHORT_ADDRESS #define CONFIG_DEVICE_SHORT_ADDRESS 10 #endif // <o> CONFIG_OTHER_ADDRESS - Peer device short address #ifndef CONFIG_OTHER_ADDRESS #define CONFIG_OTHER_ADDRESS 11 #endif
Other configuration parameters, such as whether to use a secure connection or setting the baudrate, etc. are handled in the application’s config.h file.
The UART connection requires hardware flow control, so CTS and RTS pins need to be enabled on each PAN1780 board. As such the shunt connectors on the UART breakout pins should match the picture.
Once the code has been flashed onto each board, a serial terminal like PuTTY (or equivalent) will be required for each PAN1780 board to send and receive data between the two boards.
As there really wasn't much to see to warrant a video for this part, I went straight onto creating a modification of my own to visualise what’s going on better and then test the throughput.
Well, thanks to the way the PAN1780 dev board is designed all I had to do was change the shunt connectors to get my adapted setup working. I did not need to change any of the PAN1780 code for my experiments.
Adding in a LPC1768 as an automated serial (UART) controller
For my new application I replaced the PuTTY serial terminal with a LPC1768 board as my serial (UART) controller and created some code for my project using MBED OS. The complete code is shown here (but not going into code detail in the blog):
/* mbed Microcontroller Library * Copyright (c) 2019 ARM Limited * SPDX-License-Identifier: Apache-2.0 */ #include "mbed.h" // Create UnbufferedSerial objects static UnbufferedSerial PAN1780_A(p9, p10); static UnbufferedSerial PAN1780_B(p13, p14); // Create a DigitalOutput object to toggle an LED whenever data is received. static DigitalOut led(LED1,0); // The bytes that are sent. Added "-" as a buffer at the end to allow for timing calculations (seemed to do what I wanted) const char THECHARS[15] = {'e', 'l', 'e', 'm', 'e', 'n', 't', '1', '4','-','-','-','-','-','-'}; using namespace std::chrono; milliseconds TransmitInt = 3000ms; uint64_t t_begin; // creates a queue with the default size EventQueue queue; // create a timer Timer t; static void PrintLatency(uint64_t elapse) { printf("%llu msec\r\n", elapse); } static void on_A_rxInt() { char c; PAN1780_A.read(&c, 1); if (c == '4') { t.stop(); queue.call(PrintLatency, duration_cast<milliseconds>(t.elapsed_time()).count() - t_begin); } } static void on_B_rxint() { char c; // Toggle the LED. led = !led; // Read the data to clear the receive interrupt. if (PAN1780_B.read(&c, 1)) { // Echo the input back to the terminal. PAN1780_B.write(&c, 1); //printf("%s\r\n", &c); } } static void SendByte() { char c; static uint8_t i = 0; static uint8_t cntr = 0; bool callagain = true; if (TransmitInt == 3000ms) TransmitInt = 50ms; if (i == 0) { t.start(); t_begin = duration_cast<milliseconds>(t.elapsed_time()).count(); } c = THECHARS[i]; i++; if (i == 15) { i = 0; if (TransmitInt <= 2ms) { callagain = false; } else if (TransmitInt <= 10ms) { if (cntr > 5) { TransmitInt -= 1ms; cntr = 0; } else cntr++; } else if (TransmitInt <= 100ms) { TransmitInt -= 10ms; } } if (callagain) { if (i < 10) PAN1780_A.write(&c, 1); queue.call_in(TransmitInt, SendByte); } else { t.stop(); led = 0; printf("\r\nStopped\r\n"); } } int main() { printf("PAN1780 PWAN Throughput Test\r\n"); // Set desired properties (9600-8-N-1). PAN1780_A.baud(38400); PAN1780_A.format( /* bits */ 8, /* parity */ SerialBase::None, /* stop bit */ 1 ); PAN1780_A.set_flow_control(mbed::UnbufferedSerial::RTSCTS, p8, p7); // Register a callback to process a Rx (receive) interrupt. PAN1780_A.attach(&on_A_rxInt, SerialBase::RxIrq); PAN1780_B.baud(38400); PAN1780_B.format( /* bits */ 8, /* parity */ SerialBase::None, /* stop bit */ 1 ); PAN1780_B.set_flow_control(mbed::UnbufferedSerial::RTSCTS, p12, p11); // Register a callback to process a Rx (receive) interrupt. PAN1780_B.attach(&on_B_rxint, SerialBase::RxIrq); //Set up a callback timer // First event callback, called in 5 seconds printf("Start event called\r\n"); queue.call_in(TransmitInt, SendByte); // the dispatch method executes events queue.dispatch(); }
The idea behind my application is two fold namely to bounce back and byte received from one PAN1780 device thereby creating two way WPAN communication and then to also send bytes from the other PAN1780 device at controlled timing intervals.
For the basic setup, I just attach the LPC1768 to the UART of the PAN1780 (B) and this will bounce back any characters received via the 802.15.4 WPAN from PAN1780 (A).
For the other part of the application where I get the LPC1768 to send a byte or a sequence of bytes at a defined intervals, I then connect the other PAN1780 to the LPC1780 as the nice thing about the LPC1780 is that it is has more than one UART port so it’s straightforward to connect the second device.
source: https://os.mbed.com/platforms/mbed-LPC1768/
Then, as before, the LPC1768 will also handle the receiving of the byte and bounce it back. I then created some additional code for the LPC1768 to measure how long it takes to send and then receive the bounced back byte, or character echo, via the WPAN.
The setup for my project was as follows:
My hypothesis is that at some point I will pick up noise where the bidirectional wireless transmission starts to trigger buffering and latency when a byte is not received correctly. Of course I am only sending one byte at a time and then bouncing it straight back, which is not very efficient or practical in the real work, but it should at least give us some indication of ballpark reliability/performance if using the full packet length available.
Maybe others have an opinion on this test setup.
The results from my one and only bidirectional throughput test was as follows:
So for a simple wireless UART bridge at low baud rates this is not bad and, in my opinion, is far simpler to use than Bluetooth LE.
The video demo
And here's the video demo: