How to Design Bluetooth Earphones Pairing Logic with State Machines – Part 2

Bluetooth powers most consumer devices used in our day-to-day life. Fundamentally, Bluetooth is a technology standard updated every year for improvements in speed and security. However, the operating logic for Bluetooth devices has virtually remained the same over the years.

This article is the second part of the first one where the basic pairing logic of Earphones is designed using a simple State Machine. Here, we would handle the case when the user long presses the power button with the help of a separate State Machine running in parallel to the first one.

Also, this example would showcase how running parallel State Machines facilitates breaking down a complex system into multiple event-driven State Machines running concurrently.

We would design State Machines on an abstract layer by assuming function names that perform Bluetooth-specific operations for us. The actual function names and signatures would differ from platform to platform.

Using a tool helps design State Machines faster, and ensures that future maintenance is easy. We will use FsmPro to design State Machine logic.

Design and Implementation

Let’s name the State Machine responsible for handling execution tasks when the power button is pressed during power-on as stm_power_interrupt.

As mentioned above, the software module BLE_Interface provides us with the API’s at the abstract layer which internally is assumed to call Bluetooth API’s. This helps us in skipping the intricacies of Bluetooth stack implementation and focus on the application layer design instead.

In the figure below, the Software module BT Logic which houses our State Machines interacts with the module BLE Interface for dependencies. For simplicity, it is shown with sample function calls.

The Power Interrupt State Machine

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

On Power up, Initial State is also the entry point of any State Machine. The initial condition is the optional condition to executing the initial state which means the Initial state won’t get executed till the initial condition is not satisfied. In this case, we do not have an initial condition.

In the init_power_interrupt state, we check if the power button is pressed long enough to consider it as an intentional press with the help of a minimum debounce period.

if((true == powerButtonStatus) && (getPressInterval() > MIN_DEBOUNCE_PERIOD))
{
    //Execute the init_power_interrupt state initiating the state machine
}

The screenshot below shows the implementation of the tool. Note that there can be only one initial state for any given State Machine. Also, we have defined the macro for a minimum debounce period with a value of 500 milliseconds. Any button press of less than debounce would be ignored by the State Machine.

The power button can be long pressed for 3 different purposes depending on the duration of the press in ascending order:

  • To play/pause current audio
  • To switch off the device
  • To initiate pairing with an external device

We’ll get the press interval from the abstract function getPressInterval() in milliseconds to compare the interval for which the button is pressed. The following code snippet shows we can get the interval and execute the respective code. Note that it is very important to decide the order in which conditions are placed.

if((true == powerButtonStatus) && (getPressInterval() > MIN_DEBOUNCE_PERIOD))
{
	int local_btn_time = getPressInterval();

	if(local_btn_time <= PLAY_PAUSE_DELAY)
	{
		//Execute code for play pause bluetooth operation
	}
	else if(local_btn_time <= SHUTDOWN_DELAY)
	{
		//Execute code for Turning device Off
	}
	else if(local_btn_time > SHUTDOWN_DELAY)
	{
		//Execute code for pairing external device
	}
}

Play pause Event

When the duration of the press is less than or equal to a programmable delay of the play pause event, State Machine considers it as a play pause event trigger. The Bluetooth Interface is sent a signal that a power button interrupt has been received.

State Machine then transitions to the state play_pause_triggerred() and executes the function ble_play_trigger() to play or pause the audio at the hardware level. Notice we have included the file “ble_interface.h” in the top right corner.

After executing the play pause event at the hardware level, State Machine transitions back to the initial state with a simple transition with no condition as shown above.

Power off Event

Now if the duration of the long press of the button is more than the play pause delay which is 1 second(configured at the top) but less than the shutdown delay, State Machine then transitions to shutdown state where it calls the BLE interface functions to clear cache and stop ongoing operations and perform a system shutdown.

As expected the device would shut down after the event meaning State Machine would cease to exist.

External pair Event

An external pairing event is triggered when the duration of the button press is more than 2 seconds. The Bluetooth device makes itself available for pairing to any external device after successful completion of which the newly paired device is connected for audio playback and added to the list of paired devices.

Control reaches the connected state when the connection request from the external device was successful.

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

However, In this state Earphones could lose connection with the external device for multiple reasons like the connected device getting turned off, going out of range, etc. Hence, 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 configurable time. We use a macro MAX_CONN_ERROR_RETRY with the value 60 as the retry parameter.

If the connection error sustains for more than 60 times it is checked in the transition shown below, Earphones then confirm that an error has occurred and makes themselves available again 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.

Notice the control also transfers to the initial state init_power_interrupt from the connected state on confirmation of the connection error.

Final thoughts

In this way, we’ve designed a State Machine which handles all the operations when the power button is long pressed by the user when the earphones are powered on. This State Machine is designed in such a way that it runs in parallel to any other State Machine running in parallel to handle its respective task.

You can find the project file here and get the tool here. Note that if you click run and generate code, you will get a compilable code as an output with two files stm_power_interrupt.h and stm_power_interrupt.c. You can make any changes you would like to the design made above and use the code files to integrate your main function or scheduler however you choose to interface.

Leave a Reply