How to design Bluetooth Earphones Pairing Logic with State Machines — Part 1

Bluetooth is everywhere, powering most consumer devices used in everyday life. Fundamentally, Bluetooth is a technology standard updated yearly for improvements in speed and security. However, the pairing and connection logic of Bluetooth devices has virtually remained the same over the years.

Image source: Beats

In this article, we will design and implement the pairing and connection logic of Bluetooth Earphones using State Machines. The logic relatively remains the same for most Bluetooth devices.

This example will showcase how using State Machines facilitates breaking down complex logic into smaller independent tasks. We will design the State Machine on an abstract layer by assuming function names which perform Bluetooth-specific operations for us. The actual function names and signatures would differ from platform to platform. Using a tool like FsmPro helps in designing State Machines faster and ensures convenient project maintenance.

Project Creation

To create a project in FsmPro, simply use the file menu (File ->New Project). A project would be created with the default project hierarchy. The default project hierarchy consists of a Hardware Module layer which contains a Microcontroller layer which contains a Software module layer which in turn contains a State Machine layer. The project hierarchy is simply used to structure a project and you can add, modularise or rename any element in the hierarchy as you want.

In the image below, the top-most layer is opened which shows the Hardware module as a process item; All abstract hierarchy elements are process items in FsmPro and it is possible to link them with function calls or APIs and even add global variables in the form of memory items which shall see later in the design.

Let’s name our project Bluetooth Controller with two software modules: BLE_Logic to house our state machine design and BLE interface to provide the APIs to call Bluetooth stack operations.

Design and Implementation

As mentioned above, the software module BLE Interface provides us with the API at the abstract layer which internally calls Bluetooth-specific implementation. This helps us in skipping the intricacies of Bluetooth stack implementation and focus on the application design instead.

In the figure above, the Software module BT Logic interacts with the BLE Interface module for dependencies. For the sake of simplicity, the interaction is shown with dummy function names.

Let’s name the State Machine responsible for handling execution tasks when the device is powered on as stm_power_on.

Power On State Machine

In State Machine terminology, the Initial state is called first by the system. Let’s name this state power_on.

Upon Power up, the earphones should attempt to connect to a device it was previously connected with. To achieve this, let’s use a global variable for retrieving the address of the previously connected device. Using the BLE Interface, the previous address can be retrieved in the code as follows:

unsigned long long int prev_addr;
prev_addr = getPrevDevAddr();

On the tool, the above code can be represented graphically as shown in the screen capture below. An Initial State is shown with a black dot on top of the state in FsmPro. Notice that we included the header file ble_interface.h to access Bluetooth stack APIs using the text item feature of FsmPro.

Now that we have the address of the previously connected device, let’s check if the device is available for connection. If available, Earphones would attempt to connect with it. This could be achieved with the following code:

if((prev_addr != 0) && (true == isDeviceAvailable(prev_addr)){
	bool connect_status = attempt_conn(prev_addr);
}

On the tool, this can be simply implemented with the help of a Transition. This transition would transfer the control in the State Machine from the initial power_on state to the attempt_connection state.

Now, If the previously connected device mentioned above is unavailable, Earphones would then make themselves available for connection to any paired devices by calling the function broadcast_as_available(). This is implemented in the state open_to_connect shown below.

Alternatively, the system transitions to the state open_to_connect if the connection attempt in the state attempt_connection had not succeeded for any reason. This is shown with the red transition below when the connection status returns false.

In the open_to_connect state, the device makes itself available to paired devices by calling a Bluetooth abstract function broadcast_as_available() which performs the broadcasting at the Bluetooth stack level.

State Machine control would stay in this state as long as no action is taken by a paired device interested in connecting with the Earphones. The system basically waits for an external paired device to initiate a connection request.

On the implementation level, the device keeps checking for a request from a paired device. If a request is received, Earphones attempt for connection with the help of the function attempt_conn().

When the connection to the requesting device is successful, the State Machine then transitions to the connected state as shown below.

In the connected state, device playback drivers are enabled. Earphones routinely communicate with the paired device to perform audio playback functions.

However, In this state, Earphones could lose connection with the external device for several reasons like the connected device getting turned off or the device going out of range; It is essential to continuously check for connection errors in this state.

When a connection error occurs, Earphones confirm the error by reproducing it for a certain amount of time(Configurable). We use a macro MAX_CONN_ERROR_RETRY with the value 60 as the retry parameter.

In this way, If a connection error sustains for more than 60 ticks of the controller, as is checked in the transition shown below, Earphones confirm that an error has occurred and makes themselves available for connection to paired devices.

On zooming out in the tool, the complete State Machine diagram now looks as shown below. Notice the macro MAX_CONN_ERROR_RETRY is defined in the top right corner of the figure alongside _global_conn_status — the global variable for connection status.

Note that the State Machine control transfers from the attempt connection state to the connected state had the connection succeeded in the first attempt.

Final thoughts

In this part one of this two-part series, We have designed the logic to connect Bluetooth Earphones with a previously paired device. In part 2, We will design another State Machine that runs in parallel and adds a new device to the list of paired devices when the power button is long-pressed. This article is intended to showcase the applicability of State Machines in solving a real-world problem and does not take into account all the intricacies of the Bluetooth stack.

The project file can be found in the examples section of the FsmPro. To get a compilable code, click generate code from the run menu and two output files stm_power_on.h and stm_power_on.c would be created.

Leave a Reply