Getting Started

Getting Started with SLWSTK6101 Embedded Software

The Blue Gecko Bluetooth Wireless Starter Kit (WSTK) helps evaluate Silicon Labs' Blue Gecko Bluetooth modules and get started with software development. The kits come in different versions with different module radio boards. See EFR32BG Bluetooth Module Starter Kit for details about current configurations.

To get started with the WSTK, download Simplicity Studio and the Bluetooth SDK as described in section 3. Getting Started with Simplicity Studio and the Bluetooth SDK of QSG139: Getting Started with Bluetooth® Software Development, and learn about the pre-built demos. The Bluetooth SDK comes with prebuilt demos that can be flashed to your EFR32 device and tested using a Smartphone. This document shows how to test the following prebuilt demos:

Prepare the WSTK

  1. Connect a Bluetooth Module Radio Board to the WSTK Main Board, as shown in the figure below.

  2. Connect the WSTK to a PC using the Main Board USB connector.

  3. Turn the Power switch to "AEM" position.

Note: At this stage, you might be prompted to install the drivers for the WSTK Main Board but you can skip this for now.

  1. Check that the blue USB Connection Indicator LED turns on or starts blinking.

  2. Check that the Main Board LCD display turns on and displays a Silicon Labs logo. Before starting to test the demo application, note the following parts on the WSTK Main Board

Kit Parts

Flash the Examples

  1. With your device connected as described above, open Simplicity Studio.

  2. Select your device in the Debug Adapters pane.

  3. Under the Demos column, open the Bluetooth (SoC) Basic group and select the desired demo.

  4. In the Mode drop-down in the next dialog, select Run. Click Start.

Test the Bluetooth Demos Using an Android Smartphone

Testing SoC - Empty Demo

After flashing SoC - Empty demo to your device, the device automatically starts advertising itself as "Empty Example" and can be connected to from other Bluetooth devices.

Install EFR Connect app from Google Play Store and open it. To find your advertising device, select Bluetooth Browser on the Develop tab. This shows all advertising devices nearby. By clicking on the connect button next to "Empty Example", you can connect to your device. Its GATT database is automatically discovered and displayed. Click on any service to list the characteristics inside of it and click on any characteristic to read its value.

Connecting to the SoC - Empty Demo

Testing the iBeacon Demo

Bluetooth beacons are non connectable advertisements that help you locate a device, determine your own position, or get a minimal information about an asset the beaconing device is attached to.

After flashing the iBeacon demo to your device, you can find the beacon signal with the Bluetooth Browser of EFR Connect app. Start EFR Connect and select Bluetooth Browser. To filter beacons, tap the Filter button and select the beacon types you want to be displayed. The app will provide you with basic information about the beacon, such as RSSI, which can help determine the distance of the beacon. Tap on the beacon to get more information about the data it provides.

Beaconing Demo

Testing the Health Thermometer Demo

While SoC - Empty demo implements a minimal GATT database with basic static information like device name, the Health Thermometer demo extends this database with live temperature measurements.

After flashing the Health Thermometer demo to your device, open EFR Connect app, select the Demo tab, and select Health Thermometer. Find your device advertising as Thermometer Example in the device list, and click on it to connect. The mobile phone app automatically finds the Temperature measurement characteristic of the device, reads its value periodically and displays the value on the screen of the phone.

Try touching the temperature sensor (located on the WSTK as you can see in section Prepare the WSTK). You should be able to see the temperature changing.

Health Thermometer Demo

Test the Bluetooth Demos Using an iOS Smartphone

Testing SoC - Empty Demo

After flashing SoC - Empty demo to your device, the device automatically starts advertising itself as "Empty Example" and can be connected to from other Bluetooth devices.

Install EFR Connect app from Apple App Store and open it. To find your advertising device, select Bluetooth Browser on the Develop tab. This shows all advertising devices nearby. By clicking on the connect button next to "Empty Example", you can connect to your device. Its GATT database is automatically discovered and displayed. Click on any service to list the characteristics inside of it and click on any characteristic to read its value.

Connecting to the SoC - Empty Demo

Testing the iBeacon Demo

Bluetooth beacons are non connectable advertisements, that help you locate a device, determine your own position or get a minimal information about an asset the beaconing device is attached to.

After flashing the iBeacon demo to your device, you can find the beacon signal with the Bluetooth Browser of EFR Connect app. Just start EFR Connect, and select Bluetooth Browser. To filter beacons, tap the filter button, and select the beacon types you want to be displayed. The app will provide you with basic information about the beacon, like RSSI - which can help determine the distance of the beacon. Tap on the beacon to get more information about the data it provides.

Connecting to the SoC - Empty Demo

Testing the Health Thermometer Demo

While SoC - Empty demo implements a minimal GATT database with basic static information such as the device name, the Health Thermometer demo extends this database with live temperature measurements.

After flashing the Health Thermometer demo to your device, open EFR Connect app, select the Demo tab, and select Health Thermometer. Find your device advertising as Thermometer Example in the device list, and click on it to connect. The mobile phone app automatically finds the Temperature measurement characteristic of the device, reads its value periodically, and displays the value on the screen of the phone.

Try touching the temperature sensor (located on the WSTK as you can see in section Prepare the WSTK). You should be able to see the temperature changing.

Connecting to the SoC - Empty Demo

Getting Started with BGM220 Explorer Kit

The BGM220 Explorer Kit (part number: BGM220-EK4314A) is focused on rapid prototyping and IoT concept creation around Silicon Labs BGM220P module.

Kit Overview

The kit features USB interface, on-board J-Link debugger, 1 user LED/button and support for hardware add-on boards via a mikroBus socket, and a qwiic connector.

Explorer Kit

The hardware add-on support allows developers to create and prototype applications using a virtually endless combination of off-the-shelf boards from mikroE, sparkfun, AdaFruit, and Seeed Studios. The boards from Seeed Studios feature a connector which is pin compatible with the qwiic connector but mechanically incompatible and it requires an adaption cable or board.

Testing the Bluetooth Demos

The Bluetooth SDK comes with pre-built demos that can be directly flashed into this kit and tested using a smartphone running the EFR Connect mobile app (Android, iOS):

The iBeacon can be tested with EFR Connect as documented in the above getting started section. NCP Empty can be tested with BGTool, which can be launched via the Tools dialog in Simplicity Studio 5.

GitHub Examples

Silicon Labs applications_examples GitHub repository contains additional examples that can run on the BGM220 Explorer Kit. Some of them leverage 3rd party add-on boards and they are typically found in the bluetooth_applications repository.

Porting Code from mikroSDK and Arduino

If you are using a mikroE click board then you can find ready made examples on your specific mikroE click board Web page that typically reside in mikroE's libstock and/or github. Those examples are using the mikroSDK which provides abstraction to the supported hardware kits provided by mikroE.

If you are using a board from sparkfun, Adafruit, or Seeed Studios they typically have examples for the Arduino IDE, which run on some of their own controller boards that are supported by the Arduino platform.

Those examples will not run out of the box on the BGM220 Explorer Kit, but with a small amount of effort they can be easily ported by using the guide below, which maps mikroSDK and Arduino APIs for UART/SPI/I2C/GPIO functionality into the Silicon Labs platform equivalents.

Whether porting from mikroSDK or Arduino, EMLIB and Platform Drivers/Services contain the most useful Silicon Labs APIs that. For corresponding documentation. see below:

Additionally, Silicon Labs' peripheral examples on GitHub are a good resource for simple demonstration of peripheral control using EMLIB.

mikroSDK Porting Guide

The mikroE ecosystem of click boards from MikroElektronika are typically supported by a collection of driver modules and example code that is built upon the mikroSDK. These click boards feature a mikroBUS connector for connection to a host board and can include connections for power (3.3 V, 5 V, GND), communications (UART, SPI, and/or I2C), and assorted other functions (GPIO, PWM, INT).

The mikroSDK is a framework that provides abstraction for the communication and GPIO functions of the mikroE click boards by wrapping vendor-specific functions in a common API framework to accomplish these tasks that is portable across a wide range of host devices. The microSDK is therefore ported to a new device via the assignment of function pointers and other device-specific configuration options. At this time, there is no official mikroSDK port for Silicon Labs devices.

Note: The goal of this configuration guide is not to instruct the user on how to port the mikroSDK to the BGM220 or other Silicon Labs devices, but instead to introduce the user to the spectrum of Silicon Labs' native APIs and how to use these APIs instead of the mikroSDK.

There is currently a wide selection of mikroE click accessory boards to facilitate product development with devices such as sensors, display/LEDs, storage, interface, and HMI. Some of these boards are shown below.

mikroE click boards

Using the mikroBUS-compatible socket included on the Explorer Kit BGM220, these boards can be used with the BGM220 as the host controller via the pin functions connecting the mikroBUS socket to the BGM220.

When porting mikroE click examples to the Silicon Labs platform, it is important to understand that interaction between the host controller and the click board is accomplished using a subset of UART, I2C, SPI, GPIO, analog, PWM, or interrupt. Pins for each function are allocated between the BGM220 and the mikroBUS connector, as shown below.

mikroBUS Socket

MikroE Socket SignalMikroE Socket PinBGM220 Pin or Board Connection
MIKROE_ANALOGJ201.1PB00
MIKROE_RSTJ201.2PC06
MIKROE_SPI_CSJ201.3PC03
MIKROE_SPI_SCKJ201.4PC02
MIKROE_SPI_MISOJ201.5PC01
MIKROE_SPI_MOSIJ201.6PC00
3V3J201.7VMCU - BGM220P voltage domain
GNDJ201.8GND
MIKROE_PWMJ202.1PB04
MIKROE_INTJ202.2PB03
MIKROE_UART_RXJ202.3PB02
MIKROE_UART_TXJ202.4PB01
MIKROE_I2C_SCLJ202.5PD02
MIKROE_I2C_SDAJ202.6PD03
+5 VJ202.7+5 V - Board USB voltage
GNDJ202.8GND

Note: Knowledge of the pin mapping shown above combined with use of Silicon Labs' native APIs, as shown below, enable a user to port existing click examples to the Explorer Kit BGM220 by substituting Silicon Labs API calls for mikroSDK and click driver API calls.

The following sections cover four main categories of API-enabled interactions between a host device (BGM220P in this case) and a click board: GPIO, SPI, I2C, and UART. Note that the functions and structures presented here correspond to elements of the core mikroHAL and mikroBUS APIs of the mikroSDK and their closest Silicon Labs counterparts. Many click boards have additional libraries and driver files that integrate with these layers to create the mikroE project framework. However, these core elements should help guide users porting to the Silicon Labs platform.

GPIO

The mikroSDK uses a complex system of function pointers and data structures to initialize a low level GPIO driver layer with initialization, "get," and "set" function pointers for each GPIO pin on the mikroBUS header. This structure is inherited by higher-level driver layers, which use "get" and "set" functions in higher-level wrapper functions.

By contrast, the Silicon Labs APIs for GPIO control, or em_gpio, are straightforward and easy to understand. Generally speaking, firmware should first initialize a pin and set its mode (i.e., input type, output type, and so on) by calling GPIO_PinModeSet(), then use various API calls to control or read the pin state. The following table represents a comparison and possible suggested substitution scheme for using em_gpio in place of the mikroSDK GPIO API.

Note: More em_gpio functions are available in EMLIB. For more information,see the GPIO API Documentation.

API categorymikroBUS/mikroHAL function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB Platform API
GPIOT_gpio_setFp T_gpio_obj::gpioGet[12]Pointers to Get functionsGPIO_PinOutGet(), GPIO_PinInGet()
T_gpio_setFp T_gpio_obj::gpioSet[12]Pointers to Set functionsGPIO_PinOutSet(), GPIO_PinOutClear(), GPIO_PinOutToggle()
T_mikrobus_ret mikrobus_gpioInit (T_mikrobus_soc bus, T_mikrobus_pin pin, T_gpio_dir dir)Function sets GPIO direction. This function also should be used for GPIO availability check.GPIO_PinModeSet(), GPIO_PinModeGet()
void hal_gpioMap(T_HAL_P gpioObj);Initialization of the GPIO HAL (internal mapping of function pointers and driver/HAL layer initialization)N/A
SPI

As with the mikroSDK GPIO API, the mikroSDK SPI framework relies on vendor-specific function pointers assigned in a configuration layer to provide an interface for SPI communications.

Silicon Labs offers the low-level EMLIB SPI (Synchronous USART) and higher-level EMDRV driver SPIDRV APIs for SPI communication, provided as source code. The suggested substitutions in the following table assume that the host SoC is the SPI master, however EMLIB and SPIDRV offer slave SPI functions as well.

When using EMLIB, firmware must separately configure all SPI pins to the proper mode using the EMLIB GPIO functions before using the pins for SPI communications. When using SPIDRV, however, firmware specifies the desired port and pins to use for the SPI bus in the SPIDRV_Init data structure, and configuration of these pins is handled by the SPIDRV_Init() function.

Note: More EMLIB and SPIDRV functions are available than shown here. See em_usart and and SPIDRV API documentation for more information.

API categorymikroBUS/mikroHAL function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB Platform API
SPIT_mikrobus_ret mikrobus_spiInit (T_mikrobus_soc bus, const uint32_t *cfg)Function will initialize SPI peripheral on provided MIKROBUS depending on configuration provided as second argument.USART_InitSync() or SPIDRV_Init()/SPIDRV_DeInit()
void hal_spiMap(T_HAL_P spiObj);Initialization of the SPI HAL (internal mapping of function pointers and driver/HAL layer initialization)N/A
static void hal_spiWrite(uint8_t *pBuf, uint16_t nBytes);Function should execute write sequence of n bytes.USART_SpiTransfer() (ignore return value), SPIDRV_MTransmit(), SPIDRV_MTransmitB()
static void hal_spiRead(uint8_t *pBuf, uint16_t nBytes);Function should execute read sequence of n bytes.USART_SpiTransfer() (read return value, pass dummy data parameter), SPIDRV_MReceive(), SPIDRV_MReceiveB()
static void hal_spiTransfer(uint8_t pIn, uint8_t pOut, uint16_t nBytes);Function should execute RW sequence of n bytesUSART_SpiTransfer(), SPIDRV_MTransfer(), SPIDRV_MTransferB(), SPIDRV_MTransferSingleItemB()
I2C

As with other mikroSDK modules, the mikroSDK I2C framework relies on vendor-specific function pointers assigned in a configuration layer to provide an interface for I2C communications.

Silicon Labs offers the EMLIB I2C driver em_i2c for firmware control of the I2C interface, which differs slightly in approach from the mikroSDK framework. The em_i2c firmware interface relies on the configuration of the I2C peripheral block and desired transfer parameters using the I2C_Init() and I2C_TransferInit() functions. Management of the I2C transfer state machine handled by repeated calls to I2C_Transfer(), which handles the different phases of the I2C transfer and hardware state machine.

When using EMLIB, firmware must separately configure all I2C pins to the proper mode using the EMLIB GPIO functions before using the pins for I2C communications.

Note: The em_i2c driver library provides additional functions and features beyond those described here. See the em_i2c API documentation for more information.

API categorymikroBUS/mikroHAL function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB Platform API
I2CT_i2c_readFp T_i2c_obj::i2cReadPointer to I2C Read functionI2C_Transfer()
T_i2c_restartFp T_i2c_obj::i2cRestartPointer to I2C Restart functionI2C_Transfer()
T_i2c_startFp T_i2c_obj::i2cStartPointer to I2C Start functionI2C_Transfer()
T_i2c_stopFp T_i2c_obj::i2cStopPointer to I2C Stop functionI2C_Transfer()
T_i2c_writeFp T_i2c_obj::i2cWritePointer to I2C Write functionI2C_Transfer()
T_mikrobus_ret mikrobus_i2cInit (T_mikrobus_soc bus, const uint32_t *cfg)Function will initialize I2C peripheral on provided MIKROBUS depending on configuration provided as second argument.I2C_Init(), I2C_TransferInit()
void hal_i2cMap(T_HAL_P i2cObj);Initialization of the I2C HAL (internal mapping of function pointers and driver/HAL layer initialization)N/A
static int hal_i2cStart();This function in the snippet above should execute start condition on the I2C BUS.I2C_TransferInit(), I2C_Transfer()
static int hal_i2cWrite(uint8_t slaveAddr, uint8_t *pBuff, uint16_t nBytes, uint8_t endMode);This function in the snippet above should execute write sequence, write the data inside the pBuf and execute “end” or “restart” condition, depending on the endMode argument.I2C_Transfer()
static int hal_i2cRead(uint8_t slaveAddr, uint8_t *pBuff, uint16_t nBytes, uint8_t endMode);This function in the snippet above should execute read sequence, and place the data inside the pBuf and execute “end” or “restart” condition, depending on the endMode argument.I2C_Transfer()
UART

In a similar fashion as other mikroSDK API modules, the mikroSDK UART framework relies on vendor-specific function pointers assigned in a configuration layer to provide an interface for UART communications.

Silicon Labs offers the low-level EMLIB UART (asynchronous USART) and higher-level EMDRV driver UARTDRV APIs for UART communication, provided as source code.

When using EMLIB, firmware must separately configure all UART pins to the proper mode using the EMLIB GPIO functions before using the pins for UART communications. When using UARTDRV, however, firmware specifies the desired port and pins to use for the UART bus in the UARTDRV_InitUart_t data structure, and configuration of these pins is handled by the UARTDRV_InitUart() or UARTDRV_InitLeuart() function.

Note: The EMLIB em_usart and EMDRV UARTDRV APIs provide additional functionality and support a wider feature set of the USART peripheral than is described in this porting guide. See the em_usart and UARTDRV API documentation for more information.

API categorymikroBUS/mikroHAL function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB Platform API
UARTT_mikrobus_ret mikrobus_uartInit (T_mikrobus_soc bus, const uint32_t *cfg)Function will initialize UART peripheral on MIKROBUS1 and assign proper pointers to click driver.USART_InitAsync(), UARTDRV_InitUart(), UARTDRV_InitLeuart() and UARTDRV_DeInit()
void hal_uartMap(T_HAL_P uartObj);Initialization of the UART HAL (internal mapping of function pointers and driver/HAL layer initialization)N/A
static void hal_uartWrite(uint8_t input);Write one byte over UARTUSART_Tx(), UARTDRV_Transmit(), UARTDRV_TransmitB()
static uint8_t hal_uartRead();Read one byte over UARTUSART_Rx(), UARTDRV_Receive(), UARTDRV_ReceiveB()
static uint8_t hal_uartReady();USART_StatusGet(), UARTDRV_GetReceiveStatus() and UARTDRV_GetTransmitStatus()

Arduino Porting Guide

Many open-source examples, including those designed for use with expansion boards from sparkfun, Adafruit, or Seeed Studios, use the Arduino API. This section provides a basic mapping of some of the Arduino API functions for serial communications and GPIO handling onto suggested or possible replacement calls from the Silicon Labs emlib and EMDRV libraries.

Although the Arduino API contains many submodules for different tasks, this guide focuses on Silicon Labs API replacements for GPIO control (Arduino Digital IO API), SPI communications (Arduino SPI API), I2C communications (Arduino Wire API), and UART communications (Arduino Serial and SoftwareSerial APIs).

GPIO (Arduino Digital IO API)

The Silicon Labs APIs for GPIO control, or em_gpio, provide straightforward and easy to understand functions for GPIO initialization and control. Generally speaking, firmware should first initialize a pin and set its mode (i.e., input type, output type, and so on) by calling GPIO_PinModeSet(), then use various API calls to control or read the pin state. The following table represents a comparison and possible suggested substitution scheme for using em_gpio in place of the Arduino Digital I/O API.

Note: More em_gpio functions are available in EMLIB than shown here. See the em_gpio API documentation for more information.

API categoryArduino function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB Platform API
GPIOpinMode()Configures the specified pin to behave either as an input or an output.GPIO_PinModeSet(), GPIO_PinModeGet()
digitalRead()Reads the value from a specified digital pin, either HIGH or LOW.GPIO_PinOutGet(), GPIO_PinInGet()
digitalWrite()Write a HIGH or a LOW value to a digital pin.GPIO_PinOutSet(), GPIO_PinOutClear(), GPIO_PinOutToggle()
SPI (Arduino SPI API)

Silicon Labs offers the low-level EMLIB SPI (synchronous USART) and higher-level EMDRV driver SPIDRV APIs for SPI communication, provided as source code. The suggested substitutions in the following table assume that the host SoC is the SPI master. However, EMLIB and SPIDRV offer slave SPI functions as well.

When using EMLIB, firmware must separately configure all SPI pins to the proper mode using the EMLIB GPIO functions before using the pins for SPI communications. When using SPIDRV, however, firmware specifies the desired port and pins to use for the SPI bus in the SPIDRV_Init data structure, and configuration of these pins is handled by the SPIDRV_Init() function.

Note: More EMLIB and SPIDRV functions are available than shown here. See the em_usart and and SPIDRV API documentation for more information.

API categoryArduino function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB/EMDRV Platform API
SPISPISettingsThe SPISettings object is used to configure the SPI port for your SPI device. All 3 parameters are combined to a single SPISettings object, which is given to SPI.beginTransaction()USART_InitSync_TypeDef Struct
begin()Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.USART_InitSync() with GPIO_PinModeSet() OR SPIDRV_Init()
end()Disables the SPI bus (leaving pin modes unchanged).USART_Enable() with parameter enable = false
beginTransaction()Initializes the SPI bus using the defined SPISettings.USART_InitSync() with GPIO_PinModeSet() OR SPIDRV_Init()
endTransaction()Stop using the SPI bus. Normally this is called after de-asserting the chip select, to allow other libraries to use the SPI bus.USART_Enable() with parameter enable = false, USART_Reset(), and GPIO_PinModeSet() OR SPIDRV_DeInit()
setBitOrder()Sets the order of the bits shifted out of and into the SPI bus, either LSBFIRST (least-significant bit first) or MSBFIRST (most-significant bit first).set by "msbf" element of USART_InitSync_TypeDef structure and passed to USART_InitSync(); OR set by "bitOrder" element of SPIDRV_Init structure and passed to SPIDRV_Init()
setClockDivider()Sets the SPI clock divider relative to the system clock. Depricated for Arduino??use USART_BaudrateSyncSet(), USART_InitSync(), SPIDRV_Init()
setDataMode()Sets the SPI data mode: that is, clock polarity and phase. Depricated for Arduino??set by "clockMode" element of USART_InitSync_TypeDef structure and passed to USART_InitSync(); OR set by "clockMode" element of SPIDRV_Init structure and passed to SPIDRV_Init()
transfer()SPI transfer is based on a simultaneous send and receive: the received data is returned in receivedVal (or receivedVal16). In case of buffer transfers the received data is stored in the buffer in-place (the old data is replaced with the data received).USART_SpiTransfer(), SPIDRV_MTransfer(), SPIDRV_MTransferB(), SPIDRV_MTransferSingleItemB()
usingInterrupt()If your program will perform SPI transactions within an interrupt, call this function to register the interrupt number or name with the SPI library. This allows SPI.beginTransaction() to prevent usage conflicts. Note that the interrupt specified in the call to usingInterrupt() will be disabled on a call to beginTransaction() and re-enabled in endTransaction().use USART_IntEnable() and USART_IntDisable()
shiftOut()Bit bang (software) SPI TX.USART_SpiTransfer(), SPIDRV_MTransfer(), SPIDRV_MTransferB(), SPIDRV_MTransferSingleItemB()
shiftIn()Bit bang (software) SPI RX.USART_SpiTransfer() (read return value, pass dummy data parameter), SPIDRV_MReceive(), SPIDRV_MReceiveB()
I2C (Arduino Wire API)

Silicon Labs offers the EMLIB I2C driver (em_i2c) for firmware control of the I2C interface, which differs slightly in approach from the Arduino Wire API. The em_i2c firmware interface relies on the configuration of the I2C peripheral block and desired transfer parameters using the I2C_Init() and I2C_TransferInit() functions. Management of the I2C transfer state machine is then handled by repeated calls to I2C_Transfer(), which handles the different phases of the I2C transfer and hardware state machine.

When using EMLIB, firmware must separately configure all I2C pins to the proper mode using the EMLIB GPIO functions before using the pins for I2C communications.

Note: The em_i2c driver library provides additional functions and features beyond those described here. See the em_i2c API documentation for more information.

API categoryArduino function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB Platform API
I2Cbegin()Initiate the Wire library and join the I2C bus as a master or slave. This should normally be called only once.I2C_Init()
requestFrom()Used by the master to request bytes from a slave device. The bytes may then be retrieved with the available() and read() functions.I2C_TransferInit()
beginTransmission()Begin a transmission to the I2C slave device with the given address. Subsequently, queue bytes for transmission with the write() function and transmit them by calling endTransmission().I2C_TransferInit()
endTransmission()Ends a transmission to a slave device that was begun by beginTransmission() and transmits the bytes that were queued by write().I2C_Transfer()
write()Writes data from a slave device in response to a request from a master, or queues bytes for transmission from a master to slave device (in-between calls to beginTransmission() and endTransmission()).I2C_Transfer()
available()Returns the number of bytes available for retrieval with read(). This should be called on a master device after a call to requestFrom() or on a slave inside the onReceive() handler.N/A
read()Reads a byte that was transmitted from a slave device to a master after a call to requestFrom() or was transmitted from a master to a slave. read() inherits from the Stream utility class.I2C_Transfer()
SetClock()This function modifies the clock frequency for I2C communication. I2C slave devices have no minimum working clock frequency, however 100KHz is usually the baseline.I2C_BusFreqSet(), I2C_BusFreqGet()
onReceive()Registers a function to be called when a slave device receives a transmission from a master.use I2C_IntEnable(), I2C_IntDisable(), I2C_IntGet(), I2C_IntClear(), I2C_IntGetEnabled(), I2C_IntSet(), and I2C ISR to manage I2C interrupts, if used
onRequest()Register a function to be called when a master requests data from this slave device.use I2C_IntEnable(), I2C_IntDisable(), I2C_IntGet(), I2C_IntClear(), I2C_IntGetEnabled(), I2C_IntSet(), and I2C ISR to manage I2C interrupts, if used
UART (Arduino Serial and SoftwareSerial APIs)

Silicon Labs offers the low-level EMLIB UART (asynchronous USART) and higher-level EMDRV driver UARTDRV APIs for UART communication, provided as source code. Additionally, Silicon Labs offers a higher-level driver called RetargetIo that re-targets some standard IO functions such as printf and may be useful for replacement of some Arduino Serial and SoftwareSerial functions.

When using EMLIB, firmware must separately configure all UART pins to the proper mode using the EMLIB GPIO functions before using the pins for UART communications. When using UARTDRV, however, firmware specifies the desired port and pins to use for the UART bus in the UARTDRV_InitUart_t data structure, and configuration of these pins is handled by the UARTDRV_InitUart() or UARTDRV_InitLeuart() function. Similarly, when using RetargetIo functions, USART and GPIO initialization is handled by RETARGET_SerialInit(). Note that the RetargetIo library is Silicon Labs board-specific library, which relies on board support configuration files to properly configure GPIO and peripherals used in communications.

Note: The EMLIB em_usart, EMDRV UARTDRV, and RetargetIo APIs provide additional functionality and support a wider feature set of the USART peripheral than described in this porting guide. See the em_usart, UARTDRV, and RetargetIo API documentation for more information.

Arduino Serial API porting:

API categoryArduino function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB/EMDRV/RetargetIo Platform API
if(Serial)Indicates if the specified Serial port is ready.USART_StatusGet()
available()Get the number of bytes (characters) available for reading from the serial port. This is data that’s already arrived and stored in the serial receive buffer (which holds 64 bytes).UARTDRV_GetReceiveStatus()
availableForWrite()Get the number of bytes (characters) available for writing in the serial buffer without blocking the write operationUARTDRV_GetTransmitDepth(), UARTDRV_GetTransmitStatus()
begin()Sets the data rate in bits per second (baud) for serial data transmission. For communicating with Serial Monitor, make sure to use one of the baud rates listed in the menu at the bottom right corner of its screen. You can, however, specify other rates - for example, to communicate over pins 0 and 1 with a component that requires a particular baud rate. An optional second argument configures the data, parity, and stop bits. The default is 8 data bits, no parity, one stop bit.USART_InitAsync(), UARTDRV_InitUart(), RETARGET_SerialInit()
end()Disables serial communication, allowing the RX and TX pins to be used for general input and output. To re-enable serial communication, call Serial.begin().USART_Reset(), USART_Enable() with enable = false, use GPIO functions to change pin modes; UARTDRV_DeInit()
find()Serial.find() reads data from the serial buffer until the target is found. The function returns true if target is found, false if it times out.N/A; This must be user-implemented
findUntil()Serial.findUntil() reads data from the serial buffer until a target string of given length or terminator string is found. The function returns true if the target string is found, false if it times out.N/A; This must be user-implemented
flush()Waits for the transmission of outgoing serial data to complete. (Prior to Arduino 1.0, this instead removed any buffered incoming serial data.)RETARGET_SerialFlush()
parseFloat()Serial.parseFloat() returns the first valid floating point number from the Serial buffer. parseFloat() is terminated by the first character that is not a floating point number. The function terminates if it times out (see Serial.setTimeout()).N/A; This must be user-implemented; may be possible to use stdio functions with RetargetIo to assist in implementation
parseInt()Looks for the next valid integer in the incoming serial. The function terminates if it times out (see Serial.setTimeout()).N/A; This must be user-implemented; may be possible to use stdio functions with RetargetIo to assist in implementation
peek()Returns the next byte (character) of incoming serial data without removing it from the internal serial buffer. That is, successive calls to peek() will return the same character, as will the next call to read().No API support. Firmware should issue a direct read of the USART_RXDATAXP or USART_RXDOUBLEXP peek registers.
print()Prints data to the serial port as human-readable ASCII text. This command can take many forms. Numbers are printed using an ASCII character for each digit. Floats are similarly printed as ASCII digits, defaulting to two decimal places. Bytes are sent as a single character. Characters and strings are sent as is. For example-RetargetSerial enables this functionality using stdio functions (i.e. printf(), etc.)
println()Prints data to the serial port as human-readable ASCII text followed by a carriage return character (ASCII 13, or '\r') and a newline character (ASCII 10, or '\n'). This command takes the same forms as Serial.print().RetargetSerial enables this functionality using stdio functions (i.e. printf(), etc.)
read()Reads incoming serial data.USART_Rx(), UARTDRV_Receive(), UARTDRV_ReceiveB(), RETARGET_ReadChar (void)()
readBytes()Serial.readBytes() reads characters from the serial port into a buffer. The function terminates if the determined length has been read, or it times out (see Serial.setTimeout()). Serial.readBytes() returns the number of characters placed in the buffer. A 0 means no valid data was found.USART_Rx(), UARTDRV_Receive(), UARTDRV_ReceiveB(), RETARGET_ReadChar (void)()
readBytesUntil()Serial.readBytesUntil() reads characters from the serial buffer into an array. The function terminates (checks being done in this order) if the determined length has been read, if it times out (see Serial.setTimeout()), or if the terminator character is detected (in which case the function returns the characters up to the last character before the supplied terminator). The terminator itself is not returned in the buffer. Serial.readBytesUntil() returns the number of characters read into the buffer. A 0 means that the length parameter <= 0, a time out occurred before any other input, or a termination character was found before any other input.USART_Rx(), UARTDRV_Receive(), UARTDRV_ReceiveB(), RETARGET_ReadChar (void)()
readString()Serial.readString() reads characters from the serial buffer into a String. The function terminates if it times out (see setTimeout()).N/A; This must be user-implemented
readStringUntil()readStringUntil() reads characters from the serial buffer into a String. The function terminates if it times out (see setTimeout()).N/A; This must be user-implemented
setTimeout()Serial.setTimeout() sets the maximum milliseconds to wait for serial data. It defaults to 1000 milliseconds.N/A; This must be user-implemented
write()Writes binary data to the serial port. This data is sent as a byte or series of bytes; to send the characters representing the digits of a number use the print() function instead.USART_Tx(), UARTDRV_Transmit(), UARTDRV_TransmitB(), RETARGET_WriteChar(), RETARGET_WriteString()
serialEvent()Called when data is available. Use Serial.read() to capture this data.N/A; use USART_IntEnable(), USART_IntDisable(), USART_IntGet(), USART_IntClear(), USART_IntGetEnabled(), USART_IntSet(), and USART ISR to manage USART interrupts, if used

Arduino SoftwareSerial API porting:

API categoryArduino function/APIDescriptionCorresponding Silicon Labs GSDK/EMLIB/EMDRV/RetargetIo Platform API
SoftwareSerial()SoftwareSerial is used to create an instance of a SoftwareSerial object, whose name you need to provide as in the example below. The inverse_logic argument is optional and defaults to false. See below for more details about what it does. Multiple SoftwareSerial objects may be created, however only one can be active at a given moment.N/A
available()Get the number of bytes (characters) available for reading from a software serial port. This is data that's already arrived and stored in the serial receive buffer.UARTDRV_GetReceiveStatus()
begin()Sets the speed (baud rate) for the serial communication. Supported baud rates are 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400, 57600, and 115200.USART_InitAsync(), UARTDRV_InitUart(), RETARGET_SerialInit()
isListening()Tests to see if requested software serial port is actively listening.N/A, however Silicon Labs' USART have a loopback feature that can be enabled
overflow()Tests to see if a software serial buffer overflow has occurred. Calling this function clears the overflow flag, meaning that subsequent calls will return false unless another byte of data has been received and discarded in the meantime.N/A
peek()Return a character that was received on the RX pin of the software serial port. Unlike read(), however, subsequent calls to this function will return the same character.No API support. Firmware should issue a direct read of the USART_RXDATAXP or USART_RXDOUBLEXP peek registers.
read()Return a character that was received on the RX pin of the software serial port. Note that only one SoftwareSerial instance can receive incoming data at a time (select which one with the listen() function).USART_Rx(), UARTDRV_Receive(), UARTDRV_ReceiveB(), RETARGET_ReadChar (void)()
print()Prints data to the transmit pin of the software serial port. Works the same as the Serial.print() function.RetargetSerial enables this functionality using stdio functions (i.e. printf(), etc.)
println()Prints data to the transmit pin of the software serial port, followed by a carriage return and line feed. Works the same as the Serial.println() function.RetargetSerial enables this functionality using stdio functions (i.e. printf(), etc.)
listen()Enables the selected software serial port to listen. Only one software serial port can listen at a time; data that arrives for other ports will be discarded. Any data already received is discarded during the call to listen() (unless the given instance is already listening).N/A
write()Prints data to the transmit pin of the software serial port as raw bytes. Works the same as the Serial.write() function.USART_Tx(), UARTDRV_Transmit(), UARTDRV_TransmitB(), RETARGET_WriteChar(), RETARGET_WriteString()

Starting Application Development

Developing a Bluetooth application involves defining the GATT database structure and defining the event handlers for events such as connection_opened, connection_closed, and so on.

The most common starting point for application development is the SOC-Empty example. This project contains a simple GATT database (including the Generic Access service, Device Information service, and OTA service) and a while loop that handles some events raised by the stack. You can extend both the GATT database and the event handlers of this example according to your needs.

Note: Beginning with Bluetooth SDK version 2.7.0.0, all devices must be loaded with the Gecko Bootloader as well as the application. While you are getting started, the easiest way to do this is to load any of the precompiled demo images, which come with the bootloader configured as part of the image. When you flash your application, it overwrites the demo application but the bootloader remains. Subsequently, you can build your own bootloader, as described in UG266: Silicon Labs Gecko Bootloader User Guide. The first bootloader loaded on a clean device should always be the combined bootloader.

To start developing your application, follow these steps (illustrated in the following figure):

Note: Your SDK version may be later than the version shown in the procedure illustrations.

  1. Click New Project in the Launcher perspective.

  2. Select Bluetooth SDK and click Next.

  3. Select SoC – Empty and click Next.

  4. Name your project and click Next.

  5. Verify that your selected part is shown, and select your preferred toolchain.

    Note: You should only select one toolchain. If you are using GCC, you must uncheck IAR. Otherwise, the system will revert to IAR when you generate your files. Click Finish.

Studio SoC-Empty Studio SoC-Empty 2

A visual GATT Configurator automatically appears after creating the project to help you create your own GATT database with a few clicks. Note that a Simplicity IDE perspective button is now included on the screen in the upper right hand corner.

You can create your own database at this point, or return to it later by clicking the .isc file in the Project Explorer pane on the left. For more information, see section UG365: GATT Configurator User’s Guide .

GATT Configurator

A reference for each characteristic is generated and defined in gatt_db.h (provided that you have defined an ID for it in the GATT Configurator). You can use this reference in your code to read / write the values of the characteristics in the GATT database with gecko_cmd_gatt_server_read_attribute_value() / gecko_cmd_gatt_server_write_attribute_value() commands.

Open app.c by double-clicking it in Project Explorer. You will find the event handlers in the main loop. You can extend this list with further event handlers. The full list of events – and stack commands are in the API Reference.

main.c

To build and debug your project click Debug () in the upper left corner of the Simplicity IDE perspective. It will build and download your project, and open up the Debug perspective. Click Play ( ) to start running you project on the device.

Enabling Field Updates

Deploying new firmware for devices in the field can be done by UART DFU (Device Firmware Update) or, for SoC applications, OTA DFU (Over-the-Air Device Firmware Update). For more information on each of these methods, see AN1086: Using the Gecko Bootloader with the Silicon Labs Bluetooth Applications.

Bluetooth SoC-Empty Example

The Bluetooth SoC-Empty example is a minimal project that you can use as a template for any Bluetooth application.

The term SoC stands for "System on Chip", which means that this is a standalone application that runs on the EFR32/BGM and does not require any external MCU or other active components to operate.

As the name implies, the example is an (almost) empty template that has only the bare minimum to make a working Bluetooth application.

Starting from Bluetooth SDK 2.11 and above the SoC-Empty example was restructured to allow the development of more portable code across different targets and future SDK releases. Compared with the previous SDK versions these were the two main changes:

Anatomy of the SoC-Empty Example

This example code is written to be as simple as possible without unnecessary abstraction layers. It is split into two main parts, as follows:

The reason for splitting the example is that all the automatically created initialization code is placed in main.c and the actual application code is separated into app.c. The objective is to keep application code independent of the target device and Bluetooth SDK version and decouple it from the automatically generated project in the SDK.

To enable easy porting of your application to different targets (or migrate between SDK versions), keep any edits to main.c at minimum (preferably none).

The application main entry point is in app.c. When working on a complex application, it is of course possible to split the application logic into multiple source files.

Device Initialization (main.c)

The code in main.c fills the Bluetooth stack initialization structure (_gecko_configuration_t config_) with default values. Your application can override some of the defaults if needed before initializing the stack (done in app.c)

For details about the stack configuration options, see UG136: Silicon Labs Bluetooth® C Application Developer's Guide.

The main function is compact, a shown below:

int main(void)
{
  /* Initialize device */
  initMcu();
  /* Initialize board */
  initBoard();
  /* Initialize application */
  initApp();
  /* Start application */
  appMain(&config);
}

The init functions contain initialization code for different MCU/board/app features. For more details, see the content of those functions.

NOTE: To do handle initializations, which are specific to your board, call those function from app.c so that the auto-generated files are not touched. This will make it easier to maintain or port your project.

After initialization, the main() function calls the application entry point appMain(). A pointer to the stack configuration structure is passed as an argument so that the application can modify the settings if needed. The actual stack initialization (_gecko_init()_) is called from app.c.

Application Code (app.c)

The application main entry point is in the function appMain() that has following prototype:

_void appMain(gecko_configuration_t *pconfig)_

Within this function, the stack is initialized with _gecko_init()_ and then the application runs in an infinite loop that reads events from the stack (using the blocking _gecko_wait_event()_ call) and handles those events in a switch statement.

First event to handle is always the _system_boot_ event. This is generated when the stack is initialized and is ready to receive BGAPI commands from the application. In the SoC-Empty example, the boot event handler configures the advertising timing parameters and starts advertising in connectable mode.

A few other events are handled which provide some very basic Bluetooth functionality, for example restarting advertising after the Bluetooth connection gets closed. See the SoC-Empty source code for more details.

Enabling Debug printf

This example application now supports printf over the WSTK VCOM "out-of-the-box" but it is disabled by default so that the example itself is hardware agnostic. This is to ensure that it does not cause any issues if flashed to a custom hardware as-is (assuming the project was created for an OPN and not a radio board).

Radio Board vs. OPN

NOTE: Projects for radio boards include code specific to each radio board (e.g., disabling external SPI Flash) and they also enable the PTI (Packet Trace Interface) pins so that Bluetooth traffic can be captured with the Network Analyzer.

You will find a few calls to LOG() in main function which will print some basic debugging information. To enable printf, switch to 1 the following define in app.h

/* DEBUG_LEVEL is used to enable/disable debug prints. Set to 0 to disable debug prints completely */
#define DEBUG_LEVEL 1

Once enabled, you will see a boot message coming out of the WSTK COM Port after reset, as shown below.

alt text

NOTE: When creating the project for a radio board, all UART pins will be configured correctly because the radio configuration is known. For an OPN, you will see warnings after building the code prompting you to set the correct pin mappings in hal-config-board.h.

Disabling Sleep

The example has sleep enabled by default but this can be easily disabled using the following define in app.h

/* Set this value to 1 if you want to disable deep sleep completely */
#define DISABLE_SLEEP 1

When running into issues during application development one good initial test is to disable sleep (if enabled) to see if the issues persist, as that may help narrow down the problem.

GATT Database

The SoC-Empty has a minimal GATT database where you can start adding services and characteristics as your application requires. If your device will only be used as a GATT client, the existing database should be sufficient for most applications.

Minimal GATT

For more information about creating your custom GATT database, see UG365: GATT Configurator User’s Guide and UG118: Blue Gecko Bluetooth® Profile Toolkit Developer's Guide.

OTA Support

The minimal GATT database includes the Silicon Labs OTA service and in app.c you will find the handling for the OTA control characteristic which allows putting the device into OTA DFU mode. For more information on OTA (and UART) DFU, see AN1086: Using the Gecko Bootloader with the Silicon Labs Bluetooth® Applications.