Skip navigation
Home > All Places > ModusToolbox > Blog > 2019 > September
2019

Here's another interesting post from Matt Mielke, over at Digi-Key. It's actually two posts and, together, they describe how to set up the PSoC 6 PDM-PCM converter in the ModusToolbox Device Configurator.

The first article takes you through the PDM-PCM configuration process. I like how it breaks everything down into logical steps. It's well worth a read. Go on! Off you go. I'll still be here when you get back.

Oh, you're back already. I thought it was particularly interesting how Matt identifies, and provides a workaround, for an error in the Device Configurator. The PDM-PCM conversion requires a fast, accurate clock source and that comes from the PSoC External Crystal Oscillator (ECO). In ModusToolbox 1.1 the ECO frequency range is erroneously limited to between 4MHz and 33MHz. The error check exists to prevent users from inadvertently trying to generate frequencies that the ECO block cannot handle. Unfortunately, we got the upper limit wrong - it is too conservative. Matt's workaround is great but I wanted to let everyone know that we have already fixed the problem in our upcoming 2.0 release, which sets the upper limit at 35MHz.

Here is a pic of the configurator tool from the 1.1 release, showing the erroneous error check. You won't get that problem in our 2.0 software. Happy sampling!

Setting the ECO input frequency in ModusToolbox Device Configurator

It is time to take the final step in our week-long BLE adverture. Hopefully you have a working peripheral already. You should be able to send messages from your phone and have it send messages to you. OK, it's just a find me application and battery monitor right now, but those two things tell you a lot of what you need to know to make bigger, more interesting peripherals. Our last task is to make sure your device only talks to the right people. That process is called pairing and we shall control that with a passkey.

We shall start this job in familiar territory - the BLE Configurator. Switch the view to the GAP Settings and look at "Security configuration 0". Note that more complex peripherals can implement multiple configurations but we are only making one.

Change the Security level to "Authenticated pairing with encryption". The authentication means that the pairing requires a passkey. The IO Capability should be "Display" which means the peripheral has a way for presenting a passkey to the user. In our case that is the terminal and we will write a little code to print the key. Some peripherals do not have a display and so the authentication process has to done differently - for example, the passkey can be sent to, and displayed on, the phone instead and the a simple button can be used to accept the number on the peripheral. The last edit is for Binding, which should be set to "No Bond". In real-world applications you alnmost always bond, to the point that pairing and bonding are often considered to be synonyms. Strictly, bonding is just the storage of pairing information in non-volatile memory which enables bonded phones and devices to automatically recognize each other and connect without a passkey. We are not going to bond because it's a lot easy to learn about piring without the need to force your phone and peripheral to forget each other all the time. Let's get good at pairing first!

Setting up security in the ModusToolbox BLE Configurator

Saving the configurator edits, we need to add four events to our stack_handler() function.

  • CY_BLE_EVT_GAP_AUTH_REQ occurs when the phone asks to initiate pairing. The peripheral responds by sending back the authentication method(s) that it can support (in our case this means "display").
  • CY_BLE_EVT_GAP_PASSKEY_DISPLAY_REQUEST means the central wants the peripheral to display the passkey. The phone asks the user to enter the value (6-digit code with leading zeros).
  • CY_BLE_EVT_GAP_AUTH_COMPLETE means that the phone has sent the user's passkey and it matched the displayed value.
  • CY_BLE_EVT_GAP_AUTH_FAILED means the value entered was wrong, or the user waited too long, or some other reason for not allowing pairing.

Here is the code to handle those events. Just add it into the switch statement in stack_handler().

/* Include stdio.h at the top of main.c */

#include "stdio.h"

...

char passkey_str[50]; /* Put the string declaration at the top of stack_handler() */

...

case CY_BLE_EVT_GAP_AUTH_REQ:
    Cy_SCB_UART_PutString( KIT_UART_HW, "Authenticating\r\n" );
    /* Send the authentication settings set by the BLE Configurator - GAP Settings */
    Cy_BLE_GAPP_AuthReqReply( &cy_ble_config.authInfo[CY_BLE_SECURITY_CONFIGURATION_0_INDEX] );
break;

case CY_BLE_EVT_GAP_PASSKEY_DISPLAY_REQUEST:
    sprintf( passkey_str, "Passkey requested\tKey = %06ld\r\n", ( (cy_stc_ble_gap_auth_pk_info_t*)eventParam )->passkey );
    Cy_SCB_UART_PutString( KIT_UART_HW, passkey_str );
break;

case CY_BLE_EVT_GAP_AUTH_COMPLETE:
    Cy_SCB_UART_PutString( KIT_UART_HW, "Authentication complete\r\n" );
break;

case CY_BLE_EVT_GAP_AUTH_FAILED:
    Cy_SCB_UART_PutString( KIT_UART_HW, "Authentication fail\r\n" );
break;

As you can see, there is not a lot to it. The stack does the hard part of communicating with the phone and deciding whether to pair. The call to Cy_BLE_GAPP_AuthReqReply() takes an argument that is in the generated code from the BLE Configurator. The variable cy_ble_config is a big struct containing all the user selections from that tool. The authInfo member contains the information we just provided in an array of one element (CY_BLE_SECURITY_CONFIGURATION_0_INDEX is the only element in that array because we only have one configuration). So, sending that information up to the phone is all that is required to tell it to pop up a passkey request dialog.

CySmart prompting for a passkey

Of course, we have to print the passkey to give the user a chance to guess right! That happens in the CY_BLE_EVT_GAP_PASSKEY_DISPLAY_REQUEST event. The stack generates a random number and passes it into the event handler in the eventParam argument.

When you build and program the application, you should connect to it as normal. When you try to open the Battery service the CySmart app should automatically pop up the passkey request, as above. Note that the Android version of the app does this as soon as you connect but the iPhone version waits for you to do something that requires the peripheral to respond. On the iPhone, then, you can send an Immediate Alert before pairing and, while the phone says it was sent (it was) the peripheral ignores the message (and the LED does not light). Everything is working correctly in both cases, because you need to have completed pairing before either service will be honored by the peripheral.

Here is the terminal output for a session where I got the password wrong. There is no code to enable a re-try so you need to disconnect and reconnect on order to try again. When I got it right I could send and receive messages with the usual wild abandon! When you play with this, remember to include any leading zeros when entering the passkey - you must enter all 6 digits.

PSoC BLE Peripheral output

So that's the full set of BLE exercises. I hope you followed along and had some fun with it. We have a large array of examples of how to take this further and I have attached a template for you to recreate exactly what I have done here. To use the template download and unzip it, then open the New Application dialog, select the prototyping kit, then use the "Import..." button to open the modus.mk file in the BLE_proj folder.

Happy Blootoothing!!!

If you followed along with yesterday's blog, you should have a working BLE peripheral today. You should be able to send commands from your phone to the peripheral with wild abandon. It's good to be the King! Today we are going to turn things around and send messages from the peripheral to the phone.

Before we jump in, though, you may have noticed that the CySmart App has a tendency to remember a little too much about the connected peripheral. You program in a new application but the app keeps showing the old configuration. For example, you might have struggled to get the FindMe profile to appear when you connect. In today's example we are going to add another profile so it's a good time to give you a "clear your cache" tip. CySmart remembers the peripheral name and ID after a connection. As a result it thinks it knows that the device can do... but it has changed. To force it to refresh, go to your BLE settings on the phone and clear/delete the device. If the device is not there (exact behavior depends upon the phone and OS version) just give BLE the good old off-n-on treatment. On my phone I just swipe the wee green slider widget left, and then right again, and I am good to go.

OK, so let's add the Battery service to our phone. Things start out the same way as they did yesterday. Open the Configurator and add a "Battery" service in the "Server", alongside the Immediate Alert. Then click on the "Battery Level" characteristic and make a couple of edits. For value, write "100", which corresponds to a full battery (it is a percentage). The check the "Notify" box. That last edit is the cunning part...

ModusToolbox BLE Configurator setting up a Battery service

Save your changes and go back to editing main.c. The reason why this checkbox is important is that it enables the peripheral to update the phone without the phone expressly asking for the battery level. Woooo-hoooo! We are going to write a few lines of code that run when you press the button on the kit. That code will check that there is a connection, read the current battery level, decrement it, write the value back to the GATT database, and then tell the phone what happened.

Start by creating a global to hold the connection information. We need this because BLE supports multiple connections and it is important to only send messages to the right phone. When the transferred data is bit more important that the battery level, this becomes a big deal!

static cy_stc_ble_conn_handle_t conn_handle;

Next, we need to set the variable on a connection and clear it upon disconnect. Yes, you are right, I am being a little bit lazy by not maintaining an array of handles. But my application only supports one connection at a time. And I am really lazy person. So deal with it! Here's the code in stack_handler() - notice how the eventParam for the connection event is the handle itself.

  case CY_BLE_EVT_GATT_CONNECT_IND:
    Cy_SCB_UART_PutString( KIT_UART_HW, "Connected\r\n" );
    conn_handle = *(cy_stc_ble_conn_handle_t *)eventParam;
  break;

  case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
    Cy_SCB_UART_PutString( KIT_UART_HW, "Disconnected\r\n" );
    Cy_BLE_GAPP_StartAdvertisement( CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX );
    alert( CY_BLE_NO_ALERT );
    conn_handle.attId = 0;
    conn_handle.bdHandle = 0;
  break;

Just like the Immediate Alert, we should have an attribute change handler. All mine does is print the state of the notification attribute.

void BAS_handler( uint32_t event, void* eventParam )
{
  Cy_SCB_UART_PutString( KIT_UART_HW, "Notification " );
  Cy_SCB_UART_PutString( KIT_UART_HW, ( CY_BLE_EVT_BASS_NOTIFICATION_ENABLED == event ) ? "on\r\n" : "off\r\n" );
}

 

Register the handler in the same way as before, in main().

 

  Cy_BLE_RegisterEventCallback( stack_handler );

  Cy_BLE_IAS_RegisterAttrCallback( IAS_handler );

  Cy_BLE_BAS_RegisterAttrCallback( BAS_handler );

 

One last thing before playtime. Update the button_isr() function to update the GATT and notify the phone.

  1. Check for a valid connection with Cy_BLE_GetConnectionState()
  2. Read the current battery level from GATT with Cy_BLE_BASS_GetCharacteristicValue() - note that the function takes a size and a pointer argument for the data to be read
  3. Simulate a depleting battery by decrementing the level (and wrap back to 100 should you press the button 100 times)
  4. Write the new level to the database
  5. Read the notify characteristic from GATT with Cy_BLE_BASS_SetCharacteristicValue()
  6. If it is set then push a message up to the phone with Cy_BLE_BASS_SendNotification()

Here is the code to do that. Add it after you clear the interrupt. It's a little daunting the first time but I think that the function names become obvious pretty quickly and you soon get the hang of the defines for the service index and characteristics.

 

  if( Cy_BLE_GetConnectionState( conn_handle ) == CY_BLE_CONN_STATE_CONNECTED )
  {
    uint8_t battery_level;
    uint8_t notify;

    /* Read the current battery level from GATT */
    Cy_BLE_BASS_GetCharacteristicValue( CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, sizeof( battery_level ), &battery_level );

    /* Decrement the level */
    battery_level--;
    if( battery_level == (uint8_t)(-1) )
      battery_level = 100;

    /* Write the new level back to GATT */
    Cy_BLE_BASS_SetCharacteristicValue( CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, sizeof( battery_level ), &battery_level );

    /* Is notification enabled? If yes, tell the central */
    Cy_BLE_BASS_GetCharacteristicDescriptor( conn_handle, CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, CY_BLE_BAS_BATTERY_LEVEL_CCCD, sizeof( notify ), &notify );

    if( notify )
      Cy_BLE_BASS_SendNotification( conn_handle, CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, sizeof( battery_level ), &battery_level );
  }

 

Done typing? Good, program that bad boy!

 

When you connect now, you should see a third profile - Battery Service. It shows a full battery (because we set the value to 100 in the Configurator). And there are two buttons - Read and Notify. If you press Read it looks like nothing happens because it is just reading the same value every time. Press the button on the kit and read again... it should come back with 99. Now press the notify button and repeatedly hit that kit button. Watch as the battery level steadily decreases... you can go all the way down to zero and wrap around if you wish. In fact I know you will. You cannot stop yourself!

CySmart Battery service

Well done - you have now created a peripheral device that can send and receive messages. Pretty cool no? But there is the slight problem that any Tom, Disk or Harry can make a connection. Tomorrow, I'll wrap up this series of blogs by adding a passkey to control the pairing of phone and device. As before, the updated main.c file is attached.

Did you get your kit to connect? Could you connect and reconnect? Was it cool? Are you ready to make your device actually DO SOMETHING? Good idea - let's go!

Well, hang on a second, before we add a FindMe profile to the device I have one final thought on connections... if you ever find that you cannot connect from CySmart until you reset the kit it is probably because you were not quick enough. The default length of time that the device will advertise rapidly is 30s. After that it advertises at a far slower rate for 150s until giving up entirely to save power. The expectation is that the device would implement a wakeup feature to restart advertising rather than just burning power advertising while nothing is listening. If this annoys you just go into the GAP Settings in the BLE Configurator and, in the Advertising settings section, increase the "Fast advertising timeout" value. The other solution to this problem is to be johnny on the spot and not wander off for a coffee and a donut every time you press the program button. Not judgin'. Just sayin'.

The FindMe profile is a Bluetooth SIG-defined application that is implemented by the Immediate Alert service. It's one of the simplest profiles and all it does is let a client send a single value to a GATT server. We are going to use it to light LEDs corresponding to the value sent from the central. Legal values are 0 (no alert), 1 (mild alert), and 2 (high alert). Did I mention that it is simple? All we need to do to make this work is to configure the GATT database with the service, write a handler for when an alert event happens (meaning you send a message from your phone), and a function to write messages and light LEDs, which we will call from the handler.

In the BLE Configurator, go to the GATT Settings tab. Notice that the GATT server already contains the mandatory Generic Access (GAP) , which contains the name you gave your device yesterday, and Generic Access services.

ModusToolbox BLE Configurator showing the GAP profile in the GATT database

Now let's add the service. You can do this in two ways - add the profile or just the service. For Find Me, which we are going to use, this is basically the same thing. But in more complex applications you might add an alert service to a different profile to increase it's functionality. The profile method is to select the "GATT" heading and click the green '+' button. From the menu choose "Find Me" and the "Find Me Target (GATT Server)". The easier way is to select "Server" instead of "GATT". Then choose "Immediate Alert" noting that the client or server choice is implicit. Do NOT choose the "Alert Notification" service. That is a far richer service, that you will be welcome to try once you have mastered the simple alert. Easy there, Tiger!!!

The service gets added to the GATT database and you can choose "No Alert" as the initial condition. It seems like a good idea to start the device in a nice relaxed state, doesn't it? Note that the service enables write but not read - it will not let the phone ask for the level.

ModusToolbox BLE Configurator showing the Find Me profile in the GATT database

Let's save those edits and write a little code in main.c. Let's start with a function to set the LEDs based on the alert level.

void alert( uint8_t level )
{
  Cy_SCB_UART_PutString( KIT_UART_HW, "Alert " );

  switch( level )
  {
    case CY_BLE_NO_ALERT: /* Both LEDs off */
      Cy_SCB_UART_PutString( KIT_UART_HW, "OFF\r\n" );
      Cy_GPIO_Set( KIT_LED1_PORT, KIT_LED1_PIN );
      Cy_GPIO_Set( KIT_LED2_PORT, KIT_LED2_PIN );
    break;

    case CY_BLE_MILD_ALERT: /* LED1 off, LED2 on */
      Cy_SCB_UART_PutString( KIT_UART_HW, "MILD\r\n" );
      Cy_GPIO_Set( KIT_LED1_PORT, KIT_LED1_PIN );
      Cy_GPIO_Clr( KIT_LED2_PORT, KIT_LED2_PIN );
    break;

    case CY_BLE_HIGH_ALERT: /* LED1 on, LED2 off */
      Cy_SCB_UART_PutString( KIT_UART_HW, "HIGH\r\n" );
      Cy_GPIO_Clr( KIT_LED1_PORT, KIT_LED1_PIN );
      Cy_GPIO_Set( KIT_LED2_PORT, KIT_LED2_PIN );
    break;

    default: /* Both LEDs on - should never occur */
      Cy_SCB_UART_PutString( KIT_UART_HW, "ERROR\r\n" );
      Cy_GPIO_Clr( KIT_LED1_PORT, KIT_LED1_PIN );
      Cy_GPIO_Clr( KIT_LED2_PORT, KIT_LED2_PIN );
    break;
  }
}

OK, now we need a handler. Here is the code for that. Note that, like stack_handler(), it takes an event and a parameter argument. Our code checks the event is a write to the alert characteristic so we do not do something silly if the phone is just reading the value (although we did not give it read access anyway - this is belt-and-braces safe programming). It then extracts the value into a local variable and calls the alert() function. Note that you do not need to write the value into the database - that has already been done for you by the stack.

void IAS_handler( uint32_t event, void* eventParam )
{
  uint8_t alertLevel;

  /* Alert Level Characteristic write event */
  if( event == CY_BLE_EVT_IASS_WRITE_CHAR_CMD )
  {
    /* Read the updated Alert Level value from the GATT database */
    Cy_BLE_IASS_GetCharacteristicValue( CY_BLE_IAS_ALERT_LEVEL, sizeof( alertLevel ), &alertLevel );

    alert( alertLevel );
  }
}

We are nearly done now. We have to register the handler with the stack - just like we did with stack_handler() - and that is done in main().

Cy_BLE_RegisterEventCallback( stack_handler );

Cy_BLE_IAS_RegisterAttrCallback( IAS_handler );

 

Finally, we should make sure that the alert does not out-live the connection. Call the alert() function on a disconnect event in stack_handler(), like this.

 

case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
   Cy_SCB_UART_PutString( KIT_UART_HW, "Disconnected\r\n" );
   Cy_BLE_GAPP_StartAdvertisement( CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX );

   alert( CY_BLE_NO_ALERT );
break;

Program that into the device and connect via CySmart. Instead of "No Services" you will see the GATT database (which is a way to access all services) and the Find Me profile (which presents a profile-specific interface.

CySmart profiles

Swipe to the Find Me profile and use the button to change the alert level.

CySmart Find Me profile interface

It should change the state of the LEDs and print useful messages to the terminal. Notice how the alert gets set to OFF when I disconnected.

Terminal emulator output

I have attached the C code again (as a zip) to help you cut-and-paste into your own project. Next time, we'll add a battery service, which will send messages in the other direction.. letting the phone know that the state of the peripheral has changed.

Today, I am going to start the BLE part of this series of blogs. To do that I am going to configure my GATT database, include the BLE libraries, then launch the stack and start advertising so I can connect to the kit from my phone. If that sounds daunting... it's really not. The ModusToolbox software and tools make it all very simple. Just follow the instructions and you'll be a BLE wizard faster than it takes a UK politician to jump off the Brexit bandwagon.

Start by downloading the template I created yesterday (attached) and following the instructions at the end of that blog to create a new project. Your project should look like this.

ModusToolbox IDE project files

Double-click on the "design.modus" file to open the Device Configurator. You can also click on the "Configure Device" button in the Quick Panel - your choice. In the peripherals tab check the box labeled "Bluetooth Low energy (BLE)" then look to the right-hand panel and press "Launch Bluetooth Configurator". The tool will pop up a dialog asking you to accept the change you just made and, once you accept, will gray out the device Configurator and launch the BLE Configurator (it is not safe to allow edits in both tools at the same time).

Launching the ModusToolbox BLE Configurator

This GUI has a set of tabs and we are going to use the first three. The L2CAP and Link Layer settings are not necessary for the simple, straightforward applications we are going to write. In the General tab you will see that the default GAP role is "Peripheral". That is what we want because we are going to build a peripheral device that acts as a GATT server to a phone (called the central in BLE parlance). We do not need any edits here, and we'll cover GATT tomorrow, so move onto the GAP Settings tab.

ModusToolbox BLE Configurator - General tab

GAP stands for Generic Access Profile. It controls how your device appears to the outside world and determines how two devices can (or can't) interact. In the general section choose a silicon-generated address (it will be unique to your kit) and give it a name (I almost always call my devices "bunty"... it's a long story so, not today).

ModusToolbox BLE Configurator - GAP Settings tab

In this application we are going to advertise our name to the world and invite connections. Advertising is when the device periodically emits a single, small packet (maximum of 31 bytes) so that a listening central can detect it and make a connection. Switch to the Advertisement Packet and check "Enable Local Name". Notice how your device name gets added to the packet on the right of the window. The packet contains two items - 2 bytes of required flags (which just tell the central "I'm connectable")  and, now, your device name. If you make the name too long the packet will get too big and the tool will tell you off!

ModusToolbox BLE Configurator - GAP Settings tab

Advertising infrequently is one way that BLE conserves power because the peripheral radio is off most of the time. You can set some rules for how long and how often to advertise in the Advertisement Settings section but the defaults will be OK for us. That's all we need to do right now. Save the edits by pressing the big blue button (if you are under 600 years old, that's a floppy disk... my Mom told me about them) and switch back to the ModusToolbox IDE.

Now we need to add a wee bit of middleware for the BLE. Click on the cunningly-named "Select Middleware" button in the Quick Panel. In the dialog that pops up, select two items:

  • BLE Base
  • BLE single-core mode, controller and host on CM4 only, soft FP prebuilt library

ModusToolbox Middleware SelectorThe Base is the low-level stack (including that L2CAP and Link Layer stuff we are pretending does not exist). That second one is quite a mouthful but it's just the upper layer of the stack that will be calling the event handlers we are going to write over the next few days. Press OK and the middleware gets added to your project in the "psoc6sw-1.1" folder. All we need now is some application code.Add these include files so the compiler finds the generated source files from the BLE Configurator tool and the libraries we added.

 

#include "cy_ble_gatt.h"
#include "cycfg_ble.h"

 

In main(), add this initialization code after the button ISR setup. What does it do?

  • Installs the BLE interrupt handler with a very high priority (note that the handler is from the middleware, not your code)
  • Registers your stack event handler for stack events (we will add more tomorrow)
  • Initializes and enables the BLE hardware
  • Calls the event processing function in a forever loop (this is a little lkazt because I really should go to sleep and wait for interrupts.... shhhhhh, don't tell)

 

const cy_stc_sysint_t ble_intr_config = { bless_interrupt_IRQn, ISR_PRIORITY_HIGH };
Cy_SysInt_Init( &ble_intr_config, Cy_BLE_BlessIsrHandler );
NVIC_EnableIRQ(  ble_intr_config.intrSrc );
Cy_BLE_RegisterEventCallback( stack_handler );

/* Initialize and start BLE */
Cy_BLE_Init( &cy_ble_config );
Cy_BLE_Enable();
__enable_irq();

for( ; ; )
{
    Cy_BLE_ProcessEvents();
}

 

When BLE is operating the stack generates events that can be handled (or ignored) in the event handlers that you set up. The handlers are (indirectly) called from the Cy_BLE_ProcessEvents() function. Our handler will deal with connection and disconnection events. This is the last bit of code we need to write today.

 

void stack_handler( uint32_t event, void* eventParam )
{
    switch( event )
    {
        case CY_BLE_EVT_STACK_ON:
            Cy_SCB_UART_PutString( KIT_UART_HW, "Stack on\r\n" );
            Cy_BLE_GAPP_StartAdvertisement( CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX );
        break;

 

        case CY_BLE_EVT_GATT_CONNECT_IND:
            Cy_SCB_UART_PutString( KIT_UART_HW, "Connected\r\n" );
        break;

 

        case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
            Cy_SCB_UART_PutString( KIT_UART_HW, "Disconnected\r\n" );
            Cy_BLE_GAPP_StartAdvertisement( CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX );
        break;

 

        default:
            /* Ignore the event */
        break;
    }
}

 

Looking at the code you can see that there are three interesting events and we are printing out messages to the terminal for each one.

  • CY_BLE_EVT_STACK_ON means the stack is up and running so we turn on advertising (in fast mode) for configuration 0. You may have noticed that we made changes in the BLE Configurator for "Peripheral Configuration 0" - BLE peripherals can support multiple configurations, but we are not going that deep today.
  • CY_BLE_EVT_GATT_CONNECT_IND means a connection from a central has occurred.
  • CY_BLE_EVT_GAP_DEVICE_DISCONNECTED means... wait for it... the phone disconnected and so we re-start advertising. if we forget that part then the device will not allow another connection (until you press reset).

OK, let's run this. pres the "BLE_proj Program (KitProg3)" button and watch the compiler work it;s magic, and program the kit.

Install CySmart on your phone. It is in the App store and Google Play. Open it and refresh the screen to see your device name. EXCITING!!!

CySmart showing BLE connections

Press on the name and it will connect and show you... not very much, just "No services"! Of course not, we have not enabled any functionality yet. That's for tomorrow, when I will show you how to get the peripheral to respond to alert message from your phone.

      CySmart connected to a peripheral with no services enabled

I attached the template and also a copy of the C file (main.c) as a zip file to this blog.

I recently got volunteered to take some internal engineers through the process of creating a Bluetooth LE peripheral using ModusToolbox. I figured I should share the learning with you because it's been a while since I wrote about making PSoC do interesting things. Who knows, maybe I'll make another robot dog one day? Preferably one that does not run away or spin around like it's got rabies, and that I can control without getting off the couch. Priorities...

The goal was to empower our engineers to be able to make their own BLE designs and I hope it can do the same for you. Each step adds a little more understanding; from advertising and making connections, to sending alert messages from the phone, to receiving battery notifications from the device, and setting up rudimentary security (so engineers can't misbehave in class!!!). This is a hands-on, get up-and-running quick exercise, folks, and it's going to be up to you to take the next steps once I have covered the basics!

I decided to use a CY8CPROTO-063-PROTO kit because it is small and inexpensive, but any kit with a PSoC 63 MCU device would work without much modification (just use different pins). For example, there is the CY8CKIT-062-BLE, which is an Arduino form-factor alternative to the prototyping kit. Note, however, that the other "062" kits, such as CY8CKIT-062-WiFi-BT, use a PSoC 62 MCU, which does not have BLE on-chip. Those kits couple the PSoC MCU to a 43xxx connectivity device, which is supported by a different API.

On my kit, I used the two LEDs, the user button, and a UART to write across the KitProg3 bridge to my PC terminal emulator (TeraTerm or PuTTY). Knowing that I would create many projects during the development of this material I wanted to build a template so I would not have to keep re-creating the same setup. To do that I began with the "EmptyPSoC6App" project template, targeting the CY8CPROTO kit.

Repeating the configuration steps from previous blogs (Starter Templates for ModusToolbox and Customizing your PSoC configuration (buttons and LEDs)) I opened the Device Configurator and made the following edits.

  1. Enabled pin P6[3] with the alias "KIT_LED1" and drive mode "Strong Drive, Input buffer off".
  2. Repeated that task for P7[1] and called it "KIT_LED2".
  3. Enabled pin P0[4] to support the user button, calling it "KIT_BTN1", set to "Resistive Pull-Up, Input buffer on", and with an interrupt triggered of a "Falling Edge".
  4. Finally, I enabled SCB[5] to use the UART-1.0 personality, called it "KIT_UART", with a baud rate of 115200.

For the UART connections I chose "8-bit divider 0" and let the tool figure out the divider value to get the baud rate right (handy!). Lastly I chose P5[0] and P5[1] for the RX and TX pins.

Configuring the PSoC UART for 115200 baud, with P5[0] and P5[1] as RX and TX

The tool gave me a couple of "fix-it tickets" at this step - asking me to set the drive modes for those pins. Another handy feature of the Configurator is that it will make those changes for you by clicking on the wrench (aka "spanner") icons in the Notice List.

Fix-it tickets for the UART configuration - just click on the wrench to set up the drive modes for the UART pins

Once I had everything set up I saved it, switched back to the IDE, and wrote a little code to test things out. In main() I turned on the UART, installed the button ISR, and set one LED on and the other off.

int main(void)
{
  /* Set up the device based on configurator selections */
  init_cycfg_all();

  /* Turn on the UART */
  Cy_SCB_UART_Init( KIT_UART_HW, &KIT_UART_config, &KIT_UART_context );
  Cy_SCB_UART_Enable( KIT_UART_HW );
  Cy_SCB_UART_PutString( KIT_UART_HW, "\r\n\n*** Application Started ***\r\n" );

  /* Turn on the button interrupt */
  const cy_stc_sysint_t button_intr_config = { KIT_BTN1_IRQ, ISR_PRIORITY_LOW };
  Cy_SysInt_Init( &button_intr_config, button_isr );
  NVIC_EnableIRQ(  button_intr_config.intrSrc );

  /* Turn one LED on and one off */
  Cy_GPIO_Set( KIT_LED1_PORT, KIT_LED1_PIN );
  Cy_GPIO_Clr( KIT_LED2_PORT, KIT_LED2_PIN );

  __enable_irq();

  for(;;)
  {
  }

}

Then I added an ISR to do some fairly simple stuff when I press the button.

void button_isr( void )
{
  /* Clear the interrupt */
  Cy_GPIO_ClearInterrupt( KIT_BTN1_PORT, KIT_BTN1_PIN );
  NVIC_ClearPendingIRQ( KIT_BTN1_IRQ );

  /* Toggle both the LEDs */
  Cy_GPIO_Inv( KIT_LED1_PORT, KIT_LED1_PIN );
  Cy_GPIO_Inv( KIT_LED2_PORT, KIT_LED2_PIN );

  /* Print a friendly message */
  Cy_SCB_UART_PutString( KIT_UART_HW, "Button pressed\r\n" );
}

When I programmed the kit it behaved perfectly (after 15 bone-headed iterations that I'll not discuss!) - pressing the button swaps the LEDs and prints the nice message. I have a good start point for adding BLE!

Now I want to turn this project into a template. I created a new folder and copied across three files - design.modus, modus.mk, and main.c (in the Source folder). I edited modus.mk to make it more friendly as a template with these settings:

CY_EXAMPLE_NAME = BLE_proj

CY_EXAMPLE_DESCRIPTION = Useful start point for BLE designs on the CY8CPROTO-063-BLE kit.

CY_APP_CM4_SOURCE = \

Source/main.c

Finally, I trimmed back the main.c file to remove some of the test code from above. I did not want to have to delete it every time! Specifically, I removed the LED toggling and UART printing code from the ISR as well as the LED set/clr calls in main().

With this template, that I have attached to this blog (remember that you need to log in to get it), I am ready to dive into BLE development. Before then, here are instructions on creating projects with the template.

Unpack the attached files into your workspace folder (or anywhere convenient).

1. Click on New Application from the Quick Panel

Creating a new project in ModusToolbox IDE

2. Select CY8CPROTO-063-BLE as the target kit and press Next.

Selecting the target kit in ModusToolbox IDE

3. Click on Import.. then navigate to the template folder and choose modus.mk.

4. Check that the dialog updates with the template information and press Next.

Selecting a custom project template in ModusToolbox

5. Verify the selections in the final view and press Finish to create the project.

In my next blog I'll start our BLE adventure by launching the stack and advertising to my phone!