Bluetooth is one of the easiest and most common ways to connect your embedded project over a wireless signal. Sometimes bluetooth can be used to communicate between two modules in an IoT environment or a simple controller for an embedded hobbyist, but in the case of BT Audio click it makes implementing streaming audio over bluetooth easier than ever. In this article we will go over how BT Audio is capable of making this typically difficult task very easy for us, as well as the many features and how to implement them in your own example!
How Is It So Easy?
Interfacing with the RN52 by Microchip takes some string commands to configure and a bluetooth device to connect with. With a few commands, your device is ready to connect and begin streaming audio or whatever else you have in mind. If you're familiar with streaming audio of any kind you know that this is not an easy task, but with the RN52 on the BT Audio click it takes the user a few string commands and the RN52 can decode SBC, AAC and aptX audio. With its on-board Audio DSP 16-bit Stereo CODEC and great firmware, the module takes UART commands to configure the settings, and after that the magic of outputting clean and clear audio is done by the RN52. After configuration, the device can be put into "data" mode to convert all incoming audio signals accordingly and then sends them through the integrated amplifier to the audio jacks on the click board.
What Can It Do?
Besides the initial setup and configuration like audio routing, module name, authentication pin and bluetooth profile type settings, this little chip is capable of many cool features that make developing a connected media device very effortless. Here is a list of the capabilities:
- Speaker / Mic gain
- Tone gain
- Battery Status
- Toggle Discoverable
- Voice Call Dialing
- Track Metadata
- Redial
- Increase / Decrease Volume
- Next / Previous Track
- Pause / Play
- Accept / Reject Incoming Calls
- Voice Command ( if host device is capable )
- Mute Caller
- Caller ID
Every one of these features are easily accessed by the library already developed by MikroE, with an awesome example on the Mikromedia Plus for STM32 ARM with the Mikromedia+ shield. Let's take a look at how that example is made.
Media Station Example
We have created a great example that shows how to use the Mikromedia Plus for STM32 ARM development platform, Visual TFT, MikroC and the BT Audio click to create an interface for connecting your phone or other device to the click, and playing music or making phone calls. If you'd like to just use our code, download that example from Libstock, otherwise follow these steps to create your own example.
For this example we have used the Mikromedia Plus for STM development platform on which we added a shield. An RN52 click board was added to the mikroBUS slot 1 on the shield.
Setup
To setup your workspace for developing with the Mikromedia Plus display, start up Visual TFT and choose Mikromedia_Plus_for_STM32_ARM as the platform, and mikroC PRO for ARM for your Target Compiler like so:
Objects
After your work environment is setup for this board, you can go ahead and start designing your screens to display anything you'd like. The example code is provided on Libstock, but if you'd like you can create your own buttons and text with the tools provided in TFT. To create a button, simply drag a "Button" from the Components palette to your screen...
Drag as many buttons or labels you want to the screen, and when you are finished making it look nice, you can begin to create functions for the events that can happen with your objects. In our example we use "OnClick" events for our buttons to sense when a user has pushed the button. By implementing these events, you can include any code you want inside of the functions created, therefore making a button do anything you want. To find your buttons "OnClick" event, click on your button on the screen you're working with in Visual TFT, and then head over to the left side of the screen under "Events". Here, you can click the "+" next to the "OnClick" tag, and double click on the empty box next to "Action" in the drop down menu to create an empty function for that event.
Scheme
Once this is finished, you must Generate Code and open the project in MikroC PRO for ARM. The Project Settings will be wrong for this MCU and board, and you must upload this Scheme from Libstock and use it for this project. Once you have downloaded the scheme, navigate to Project->Edit Project... to load the scheme. On the right side of this window, click the Load Scheme button and find your scheme, in our case the scheme was downloaded into the path under /Mikroelektronika/mikroC PRO for ARM/Schemes/:
BT Audio Library Functions
You now have a working Mikromedia Plus project in MikroC. Now, you must implement some button events with the BT Audio library. You can either download the source code from Github or install the package from Libstock and include the library in your project that way. Once you've included the library, the first step would be to setup the environment for the click board. You can either copy and paste it from the already made example, or from right here:
void system_setup( void ) { GPIO_Digital_Output(&GPIOC_BASE, _GPIO_PINMASK_3 ); //BT_AUDIO_RST GPIO_Digital_Output(&GPIOD_BASE, _GPIO_PINMASK_12 ); //BT_AUDIO_CMD //Made new GPIO MODULE specifically for UART on mikrobus1 on shield for mikromedia UART2_Init_Advanced( 9600, _UART_8_BIT_DATA, _UART_NOPARITY, _UART_ONE_STOPBIT, &_GPIO_MODULE_UART2_PD5_A3); Delay_ms(300); bt_audio_init(); //Initializes HAL functions / RST toggle Delay_ms(300); RXNEIE_USART2_CR1_bit = 1; //UART Interrupt enabling NVIC_IntEnable( IVT_INT_USART2 ); EnableInterrupts(); }
It doesn't matter if you used our example or are writing one yourself... You will need to create a new "Module_Struct" for the UART on the shield.
This is how to create a new Module_Struct:
- The file is called __Lib_GPIO_32F7xx_Defs.c but the fastest way to find it is to right click on an already existing Module_Struct like _GPIO_MODULE_UART1_PA9_10 and choose Find Declaration.
- As you can see, there are many other definitions for these GPIO modules. Simply paste this code between any of the modules:
_GPIO_MODULE_UART2_PD5_A3 = { {_GPIO_USART2_TX_PD5, _GPIO_USART2_RX_PA3, -1}, {_USART_CONFIG, _USART_CONFIG, -1} },
Event Functions
When you were in Visual TFT and created the "Events" functions, it created a pointer to these functions in your "project_name_events_code.c" file. You can include the "bt_audio_hw.h" file in this file to access functions for controlling the BT Audio. The names for the functions that handle the events should directly relate to the button they are connected to, unless you decided to change the name of them at the time of creation before clicking "Generate Code". Let's take a look at one of the event functions in the example project:
void play_btnOnClick() { connection_status_t connection; bt_audio_query_connection_status( &connection ); if ( !connection.A2DP_connection ) return; if ( music_state_t == PAUSED ) { bt_audio_pause_play_track(); music_state_t = PLAYING; if ( first_song ) { get_track_data(); first_song = false; } } }
In this case, there is a little bit of extra code. The connection_status_t is a struct that is used to determine the connection status through the function bt_audio_query_connection_status() . This code is here to make sure the program is not attempting to do things with the BT Audio click that it is not prepared to do yet. With that said, first this function checks the connection status, and if there is not a connection, it does nothing. Since the BT Audio is only capable of toggling play/pause, we must implement a check to determine if the music is playing or not. It does this by checking an enum that is used to define the current state of the music at all times ( these types of enums are described in more detail later ). It then uses the bt_audio_pause_play_track() function to either play or pause the current track playing over the BT Audio click. One last flag is implemented to tell the check_state() function whether this button push was the first. Lastly, the get_track_data() function is called to display the new track meta data onto the TFT.
Here is one more example of an event function:
void info_btnOnClick() { screen_state_t = INFO; }
This function is a very simple example that will lead us into the next topic. When the "Info" button is pressed, this function is called. All that it does is change the screen_state_t enum to INFO . Later on, when the check_state() function is called again, it will poll this variable and react accordingly!
Small State Machine
To make implementing thread-like functionality a little bit easier, I implemented a small and simple state machine. In most cases state machines would also implement some kind of transition control, but in our case the states are controlled by buttons in every possible case. Enumerated types are great for defining what the current state of something is, and in this case there are four different variables that we must watch: the current screen being displayed, call state, connection state, and music state. The enums used for determining these states look like this:
enum { AUDIO, INC_CALL, PHONE, INFO } screen_state_t; enum { CALL_TRUE, CALL_FALSE } call_state_t; enum { CONN_TRUE, CONN_FALSE } conn_state_t; enum { PLAYING, PAUSED } music_state_t;
Let's take a look at the screen_state_t enum. There are four screens, so therefore there are four different possibilities for the current state of the screen. To poll these states, a function is called from the main while loop that checks each state in a switch statement, and handles each different case accordingly. This makes the readability of code much easier to walk through by giving each state a descriptive name. The switch statement makes it even easier to control each individual state accordingly by giving each enum value its own space for user-defined code. Here is what that function looks like:
void check_state( void ) { static uint8_t old_screen_state; static uint8_t old_call_state = CALL_FALSE; static uint8_t old_conn_state = CONN_FALSE; connection_status_t connection; uint8_t txt_buffer[40] = { 0 }; bt_audio_query_connection_status( &connection ); if ( connection.incoming_call ) //The connection must be checked for a incoming call. This must happen as fast as possible. screen_state_t = INC_CALL; if ( old_screen_state == screen_state_t ) //New Screen has not been selected { switch ( screen_state_t ) //Current Screen { case AUDIO: //Audio Screen if ( connection.connected || connection.A2DP_connection ) conn_state_t = CONN_TRUE; else conn_state_t = CONN_FALSE; if ( old_conn_state != conn_state_t ) { if ( conn_state_t == CONN_TRUE ) { Circle1.Color = 0x07E0; DrawCircle( &Circle1 ); } else { Circle1.Color = 0xF800; DrawCircle( &Circle1 ); } old_conn_state = conn_state_t; } break; case INC_CALL: //Incoming Call Screen bt_audio_display_caller_id( txt_buffer ); caller_id_name.Caption = txt_buffer; DrawLabel( &caller_id_name ); break; case PHONE: //Phone Dialing Screen break; case INFO: //Info Screen break; default: break; } } else //If screen state has changed { switch ( screen_state_t ) { case AUDIO: //Audio Screen DrawScreen( &audio_screen ); break; case INC_CALL: //Incoming Call Screen DrawScreen( &incoming_call_screen ); break; case PHONE: //Phone Dialing Screen DrawScreen( &phone_screen ); break; case INFO: //Info Screen assign_all_settings(); DrawScreen( &info_screen ); break; } old_screen_state = screen_state_t; //Set "old" state to new states } }
There is a static variable for each unique state machine, to determine the "old" or "previous" state of the object. The variables to hold these "old" states are static so that they stick around and can be remembered whenever this function is called. The next important thing to realize is that the function is split up into two switch statements. One switch statement is used when the screen state is the same as it was in the last call of the function, and the other is used for when the screen state has changed and the display must be changed before continuing. This also provides protection from trying to modify things from other screens that are not being displayed currently.
Main
Our main function should only be used to setup the BT Audio and anything else needed before starting the TFT display and touch functions. Here is our implementation:
void main() { uint8_t buffer[20] = { 0 }; system_setup(); bt_audio_set_cmd_mode( 0, buffer ); Delay_ms(300); Start_TP(); assign_default_settings(); while (1) { Check_TP(); check_state(); } }
Other than system_setup() there is not much going on here. Everything needed to setup is setup through the system_setup() function, then the BT Audio is set to data mode, some default settings are initialized with assign_default_settings() and finally the TFT is initialized. Inside of our while loop, the TFT is checked, and a call to the check_state() function that we reviewed earlier is made.
Summary
With the BT Audio click, you can make wireless connectivity with audio streaming and easy control as simple as a few string commands. Take a moment to play with some of the cool features in Visual TFT and other features you can implement with the RN52 and make your own examples! Don't forget to post your example code on Libstock so everyone can see what you've done and build onto it.