Skip navigation
Home > All Places > ModusToolbox > Blog > Authors MarkS_11
1 2 Previous Next

ModusToolbox

19 Posts authored by: MarkS_11

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!

Our friend, Matt Mielke at Dig-Key, just wrote a really good blog about using CMSIS-DSP on PSoC 6. It is one of those really good articles that just gets on with the job of describing the subject without a load of unnecessary, showy-offy acronyms and buzzwords. I really liked it and recommend you give it a read. Unless you really like slow math in your programs. Ok, then... good luck with that.

I liked that it methodically explains what the library does and how it helps, uses the CMSIS-PACK plug-in that ships with ModusToolbox to install the library, then adds it to the project and sets the include path up. In case you have never seen it, here is an image showing the Pack Manager after the library has been installed - it is a great way to install libraries and keep them up to date.

CMSIS Pack Manager installing CMSIS-DSP in ModusToolbox

I only thought of one thing I would add to the article. Matt explains the difference between hardfp and softfp libraries, and points out that you cannot mix-and-match them. But I'd also add that you need to remember that when choosing other middleware for your application. Here is a picture of the ModusToolbox Middleware Selector... notice the CapSense and Segger emWin entries that list a Hard FP and Soft FP implementation... you need to make sure all those match your DSP library selection.

Hard and Soft FP libraries in ModusToolbox Middleware Selector

Did you finish reading it yet? Good, wasn't it?

MarkS_11

Mbed OS 5.13.1 released

Posted by MarkS_11 Jul 18, 2019

Arm announced a patch release of Mbed OS yesterday. It is version 5.13.1 and it adds a few interesting items for PSoC users. The big change is a new Wi-Fi Host Driver (WHD), which replaces the monolithic build of our WICED stack used in previous releases with a smaller footprint, more flexible driver that will save memory in your applications and make it easier to support your own target boards. There is also a USB peripheral driver, which extends the choices you have for wired connectivity, and a new Cypress HAL that complements the C++ Mbed peripheral driver interface with an efficient, Mbed-friendly set of drivers in the C language. Lovely!

One of the nice things about an online ecosystem like Mbed is that you do not need to go through a lengthy installation process to get the new software. Simply creating a new application in Mbed CLI ("mbed new my-super-new-application") will pull in the latest and greatest version of Mbed OS. You can then confirm the OS version by running "mbed ls". Here is the output from my Mbed CLI session where I create and build a new application.

yfs@YFS-T550 MINGW64 ~/mbed4

$ mbed new my-super-new-application

[mbed] Working path "C:\Users\yfs\mbed4" (directory)

[mbed] Program path "C:\Users\yfs\mbed4"

[mbed] Creating new program "my-super-new-application" (git)

[mbed] Adding library "mbed-os" from "https://github.com/ARMmbed/mbed-os" at branch/tag "latest"

[mbed] Updating reference "mbed-os" -> "https://github.com/ARMmbed/mbed-os/#5941d1718339116cd12914238ec331c84da3..."

yfs@YFS-T550 MINGW64 ~/mbed4

$ cd my-super-new-application

yfs@YFS-T550 MINGW64 ~/mbed4/my-super-new-application (master)

$ mbed ls

[mbed] Working path "C:\Users\yfs\mbed4\my-super-new-application" (program)

my-super-new-application ( revision in the current branch)

`- mbed-os (#5941d1718339, tags: latest, mbed-os-5.13.1)

yfs@YFS-T550 MINGW64 ~/mbed4/my-super-new-application (master)

$ cp ../main.cpp .

yfs@YFS-T550 MINGW64 ~/mbed4/my-super-new-application (master)

$ mbed compile --target CY8CKIT_062_WIFI_BT --toolchain GCC_ARM

Building project my-super-new-application (CY8CKIT_062_WIFI_BT, GCC_ARM)

Scan: my-super-new-application

Compile [  0.1%]: mbed_tz_context.c

Compile [  0.2%]: MCR20Drv.c

Compile [  0.3%]: at24mac.cpp

Compile [  0.4%]: NanostackRfPhyAtmel.cpp

Compile [  0.5%]: NanostackRfPhyMcr20a.cpp

Compile [  0.6%]: rf_configuration.c

Compile [  0.7%]: fslittle_test.c

Compile [  0.8%]: main.cpp

<Lots of compilation - deleted!>

Compile [ 99.7%]: USBSerial.cpp

Compile [ 99.8%]: OperationListBase.cpp

Compile [ 99.9%]: PolledQueue.cpp

Compile [100.0%]: TaskBase.cpp

Link: my-super-new-application

Elf2Bin: my-super-new-application

Post-Build: my-super-new-application

| Module           |         .text |       .data |        .bss |

|------------------|---------------|-------------|-------------|

| [fill]           |       80(+80) |       9(+9) |     35(+35) |

| [lib]\c.a        | 16696(+16696) | 2472(+2472) |     89(+89) |

| [lib]\gcc.a      |   3168(+3168) |       0(+0) |       0(+0) |

| [lib]\misc       |     224(+224) |       4(+4) |     28(+28) |

| [misc]           |         0(+0) |       0(+0) |       0(+0) |

| main.o           |     192(+192) |       0(+0) |       4(+4) |

| mbed-os\drivers  |       74(+74) |       0(+0) |       0(+0) |

| mbed-os\features |     204(+204) |       0(+0) |     48(+48) |

| mbed-os\hal      |   1248(+1248) |       4(+4) |     67(+67) |

| mbed-os\platform |   2892(+2892) |   260(+260) |   220(+220) |

| mbed-os\rtos     |   6468(+6468) |   168(+168) | 5973(+5973) |

| mbed-os\targets  | 17578(+17578) |   107(+107) | 1236(+1236) |

| Subtotals        | 48824(+48824) | 3024(+3024) | 7700(+7700) |

Total Static RAM memory (data + bss): 10724(+10724) bytes

Total Flash memory (text + data): 51848(+51848) bytes

Image: .\BUILD\CY8CKIT_062_WIFI_BT\GCC_ARM\my-super-new-application.hex

[mbed] Working path "C:\Users\yfs\mbed4\my-super-new-application" (program)

 

Note that, after creating the application, I I just copy in a simple blinky application (main.cpp) to the my-super-new-application folder. Here is the code for that... isn't it super!?!

 

/* mbed Microcontroller Library
* Copyright (c) 2018 ARM Limited
* SPDX-License-Identifier: Apache-2.0
*/

#include "mbed.h"

DigitalOut led1(LED1);

#define SLEEP_TIME_MS 500

// main() runs in its own thread in the OS
int main()
{
    while( true )
    {
        // Blink LED and wait 0.5 seconds
        led1 = !led1;
        wait_ms( SLEEP_TIME_MS );
    }

}

MarkS_11

Task-Aware Debugging

Posted by MarkS_11 Jun 30, 2019

I was asked a question last week about multi-threaded debugging in ModusToolbox IDE. I knew there was a plug-in for that but I had never used it. So I gave it a shot and here's how you can enable task-aware debugging with the FreeRTOS kernel.

It turns out that you do not need to install the plug-in - it is already there - but you do need to enable it for OpenOCD. I did that in the file ModusToolbox_1.1\tools\openocd-2.1\scripts\target\psoc6_common.cfg. On line 228, make this addition:

${TARGET}.cm4 configure -rtos auto -work-area-phys $_WORKAREAADDR_CM4 -work-area-size $_WORKAREASIZE_CM4 -work-area-backup 0

The "auto" is taken to mean FreeRTOS by the plug-in, but you can use "-rtos FreeRTOS" if you prefer to be explicit. Next you need to define a variable that the plug-in expects (but is no longer defined in FreeRTOS). I just created a global variable like this:

static volatile int uxTopUsedPriority;

Then, in main(), I use the variable to make sure it is not optimized out by the compiler.

uxTopUsedPriority = configMAX_PRIORITIES - 1;

All I needed now was a project to debug. I created one from the PioneerKitAppFreeRTOS template and made a copy of the "blinky" task (so my application would be slightly more interesting). When I run the debugger and hit a breakpoint in one of the blinky functions, the Quick Panel Debug tab looks like this, which make it really easy to figure out what tasks are in the application and which one I am debugging.

Task-Aware Debugging in ModusToolbox IDE

Now, there is one slight problem with this... it breaks debugging of non-FreeRTOS applications! Oopsy! The reason for this is that the "-rtos auto" causes the debugger to look for missing symbols when you are not using the RTOS. I fixed this by making copies of psoc6.cfg and psoc6_common.cfg (called psoc6_rtos.cfg and psoc6_common_rtos.cfg) in the target folder above. In the psoc6_rtos.cfg file I edited the last line as follows:

source [find target/psoc6_common_rtos.cfg]

In the original psoc6_common.cfg file I backed out the edit so that this file is returned to its original state. Now I have RTOS and non-RTOS config files and, by default, the launch configuration for your projects will use the non-RTOS setup. However, when I do want to enable multi-thread debugging, I just edit the Debug Configuration (Run menu) to use the new files instead. In the Debugger tab there is a filed for Config Options and I change the config file as follows:

-c "source [find target/psoc6.cfg]"

Here is an image of that change.

Enabling Task-aware Debugging in ModusToolbox IDE

 

I am really happy with this discovery. I can now enable task-aware debugging for a project in just a couple of minutes. I think it will become a standard practice for me. I hope you like it too! I attached my config files so you can just copy them into the target folder, rather than edit the installed IDE files (remember to log into cypress.com to access them).

MarkS_11

PWM Clock Sharing

Posted by MarkS_11 Jun 27, 2019

Did you finish your homework? Well, here's an answer anyway. A couple of days ago I used a pair of TCPWM blocks to toggle the brightness of an LED at 1Hz. In that project I used an 8-bit clock divider as the source for the LED-driving PWM and a 16-bit divider for the slower toggling Timer-Counter. It worked just fine but it felt a little decadent to use two clock dividers when I am using a PSoC, which has the on-chip flexibility to use one divider for both TCPWM blocks. How to do that?

Well, the easy part is changing the clock source selection in the Timer-Counter using the Device Configurator. Open the project in ModusToolbox IDE and open the Configurator. In the SWAP_CTR block change the Clock Signal to "8 bit Divider 2 clk", which is marked in the drop-down as "USED" as a reminder that another resource is using that source (obviously... this is why we are choosing it!).

Choosing a USED PSoC clock divider

Next, there is a little mathematics, which is the English word for math. The frequency of the TCPWM output is a function of the peripheral clock (CLK_PERI) frequency, the 8 but divider value, the TCPWM prescaler (which I hinted at last time) and the period of the TCPWM counter. In pseudo-math (note that it is OK to call it math when it's "pseudo" - just because I am a snob does not mean I am not really lazy as well) it looks like this.

CLK_PERI

LED frequency =  ----------------------------

divider * prescaler * period

Some of these are known (or the pseudo-math becomes "guessing"). The LED toggling frequency needs to be 1Hz. The CLK_PERI is set to 72MHz in the Platform tab of the Device Configurator and let's leave that alone. For the others, the divider is a 8-bit value, the period is 16-bit, and the prescaler is 1, 2, 4, 8, 16, 32, 164 or 128. The trick we have to perform is to divide and prescale CLK_PERI such that the TCPWM period gives us 1Hz, preferably with a nice round number. Here is the rearranged formula.

divider * prescaler * period = 72000000

The 16-bit TCPWM has a maximum period of 65535, which means the clock must be divided by at least 1098.6, which cannot be done in a 8-bit divider. So we need to use a prescaler value of at least 8. Beyond that there are gabillions of combinations that satisfy the equation. After a little trial and error, well mostly error, I figured out that a divider of 225 works really nicely because it creates a frequency of 320kHz. Precaling that by 32 gives me a TCPWM frequency of 10kHz and a period of 10000 gives me a 1Hz output. Et voila!

Dividing and prescaling the PSoC clock frequency

Here is the set up for the Timer-Counter, with the prescaler and period set as above.

Setting the PSoC Timer-Counter period and prescaler

Note that the LED_PWM just has to toggle fast enough not to flicker so I set the period to 1000 and the compare values to 500 and 50 for the 50% and 5% intensity values.

I had quite a lot of fun doing this project. I think it nicely illustrates how flexible PSoC is and how wretched my mathematics skills are. I've attached a template that you can use to replicate the design - to use it, open the New Application dialog and "Import..." the modus.mk file to create a copy of the project.

Importing the project template into ModusToolbox IDE

Note: you must be logged into cypress.com to see the attachment (PWM_Swap.zip).

MarkS_11

Internally-Triggered PSoC PWM

Posted by MarkS_11 Jun 26, 2019

I got "back on the blog" about a week ago and wrote about configuring PWMs and hooking them up to LEDs. It was simple stuff but hopefully got us all up to speed on configuring peripherals and connecting them to pins. Today I want to extend that learning a little bit and use one TCPWM block to control another. Wooooooo! Scary stuff! Not really.

I also mentioned last time that the TCPWM supports two compare values, which is why I suggested you play with the duty cycle using the SetCompare0() function. Yes, there is also a SetCompare1(). Genius! So I propose to set up the PWM with two compare values, giving us a bright and a dim LED. Then I will periodically switch between the two values to prove that it all works. As with cat-skinning, there are many ways to do this. Here are three that come to mind (all of them assume you have set up a 16-bit PWM, called LED1_PWM, to drive an LED as in my previous blog).

  1. Modify the compare value from firmware
  2. Set up two compare values and switch between them from firmware
  3. Set up two compare values and use another TCPWM to switch between them

Let's walk through all three methods, starting with the firmware. Remember that LED1_PWM has a period of 32767 and a compare value of 16384, and that it is turned on with the following code in main().

 

Cy_TCPWM_PWM_Init(     LED1_PWM_HW, LED1_PWM_NUM, &LED1_PWM_config );

Cy_TCPWM_PWM_Enable(   LED1_PWM_HW, LED1_PWM_NUM );

Cy_TCPWM_TriggerStart( LED1_PWM_HW, LED1_PWM_MASK );

 

That gives us a fairly bright LED (actually 50% of maximum) and so, in the main loop, add these two lines of code.

 

for(;;)

{

Cy_TCPWM_PWM_SetCompare0( LED1_PWM_HW, LED1_PWM_NUM, 1638 ); // 5%

Cy_SysLib_Delay( 500 );

Cy_TCPWM_PWM_SetCompare0( LED1_PWM_HW, LED1_PWM_NUM, 16384 ); // 50%

Cy_SysLib_Delay( 500 );

}

 

Program that code and you will see the LED switch between 50% and 5% brightness. But, let's face it, that is a fairly ugly solution because we are just jamming values into the PWM and spinning the CPU.Let's move onto the second method. Open up the Device Configurator and navigate to the setup for LED1_PWM. Check the Enable Compare Swap box, which causes the Compare 1 parameter to become editable, and set it to 1638.

Setting two compare values in the PSoC TCPWM

Save that change and go back to ModusToolbox IDE. Change the C code in the loop as follows.

 

for(;;)

{

Cy_TCPWM_TriggerCaptureOrSwap( LED1_PWM_HW, LED1_PWM_MASK );

Cy_SysLib_Delay( 500 );

}

 

This code causes the PWM to swap the compare value between the choices you made in the Configurator. It's a little neater than the first choice but I am still troubled by the spin loop.Let's just do it all in hardware. Go back to the Device Configurator and find the Swap Input parameter in the Inputs section. Choose Rising Edge from the drop-down menu. This selection enables the Swap Signal parameter. Use that drop-down to choose another TCPWM block - Counter 15 is a good choice because it is adjacent to the one we are already using (and so is easy to find - there is no technical advantage to a particular 16-bit TCPWM choice). Note that there are three connectivity options on the block - overflow, underflow and cc_match/capture/compare. I chose underflow so I can trigger the swap with a down-counter. All this sets up LED1_PWM to switch between Compare 0 and Compare 1 whenever there is a rising edge on the TCPWM #15 underflow signal.

Setting up the PSoC TCPWM compare value swap signal

The next step is to set up the new TCPWM block. I chose the Timer-Counter personality - I do not need it to be a PWM because I just want the terminal condition to cause the trigger - and called it SWAP_CTR.

Choosing the Timer-Counter personality for the PSoC TCPWM

In the parameters, set the Count Direction to Down so it will underflow at the end of each cycle.

Setting the direction and period of the PSoC Timer-Counter

I want a 500ms period and so this means I need a pretty slow clock and I cannot do that with an 8-bit source divider. For the Clock Signal choose 16-bit Divider 1, jump the Peripheral-Clocks tab, and set the divider on that clock to 36000 (which give a 2kHz signal from the 72MHz source).

Setting the 16-bit divider on the PSoC Timer-Counter peripheral clock

Back in the TCPWM set the period to 1000 (shown above) and we have the desired triggering rate. Save the edits and return to the ModusToolbox IDE.First of all, remove ALL the code from the for(;;) loop - this is hardware only! Then enable the new counter as follows.

 

Cy_TCPWM_PWM_Init(       LED1_PWM_HW, LED1_PWM_NUM, &LED1_PWM_config );

Cy_TCPWM_PWM_Enable(     LED1_PWM_HW, LED1_PWM_NUM );

Cy_TCPWM_Counter_Init(   SWAP_CTR_HW, SWAP_CTR_NUM, &SWAP_CTR_config );

Cy_TCPWM_Counter_Enable( SWAP_CTR_HW, SWAP_CTR_NUM );

Cy_TCPWM_TriggerStart(   LED1_PWM_HW, LED1_PWM_MASK | SWAP_CTR_MASK );

 

There are three things to note about this code.

  1. The APIs are slightly different - i.e. Cy_TCPWM_Counter_Init intead of Cy_TCPWM_PWM_Init - but the arguments are the same.
  2. There is only one call to the TriggerStart() function because both counters are in the same block (i.e. LED1_PWM_HW == SWAP_CTR_HW).
  3. TriggerStart() acts upon the ORed masks of the two TCPWM blocks.

Programming this should cause the LED to swap states automatically without any SW intervention. I think you'd agree that it is a cleaner, more efficient solution. The only remaining problem I have with this is that I am being a little greedy with the clocks. I'd really like to use just one 8-bit clock source, as opposed to one 8- and one 16-bit divider. So I will leave you a little homework... try to change the settings in the Device Configurator to use just one clock for the TCPWMs. I'll post a solution in a day or two (assuming I can actually get it to work!).

Hint - there is a Clock Prescaler in the TCPWM blocks which can be used to slow down the input clock speed.

Back in February I committed to write about peripheral setup using the ModusToolbox device configurator tool. I have not done very well! We have had another release since then - ModusToolbox 1.1 - and I have been buried in planning for the 2.0 release and training our internal teams. But that is real work and I've had enough of it. It's time to get back to writing about PSoC and developing cool applications.

But first a word or two about a change in the project structure for PSoC applications between the 1.0 and 1.1 releases. In 1.0 we created five projects for a single application; one each for the CM4 and CM0+ cores, one each for the PDL libraries for each core, and one for the configuration. Early feedback was that this was confusing and difficult to manage and so we re-jigged things in the 1.1 software. We also found that most engineers do not use the CM0+ core in real applications because they prefer the faster (more power efficient) CM4 and the simplicity of single-core development. As a result, by default, we now only create the CM4 project and leave the CM0+ to the experts. That removed two of the required projects, which was a good start. Next, we realized that there is no longer any point having a separate configuration project, so we moved the design.modus file into the CM4 project and put all the generated source into a local folder, instead of a link to another project. The result is a major usability improvement. You get the application project, where you do all the work, and a library project, which you typically never edit - just build once and link. Another benefit of this is that the library project can be linked to any number of a applications. The first application you create in a new workspace will have a library project but, if you then create more applications they default to sharing the original library. I find this really helps me manage projects and I only rarely have to change, or create new, workspaces.

In previous posts I have written about creating applications, starter templates for single-threaded and FreeRTOS applications, and doing some basic things like twiddling LEDs and reacting to button presses. Note that in the post about <here all the generated code goes I wrote about the GeneratedSource folder being in the configuration project. Obviously, if you are using ModusToolbox 1.1 that folder is now in the main application project.

OK, that's enough housekeeping about releases. We've all got v1.1 right! Good, let's modify a start project to set up a peripheral. You all know how to do this by now (check out the links above if you need a reminder) so create a new application using the PioneerKitApp starter template. This template has the buttons and LEDs configured for us so we do not need to repeat all that work.

Open the device configurator (click on "Configure Device" in the Quick Panel or double-click on design.modus) and look for KIT_LED1 in the Pins tab. Click on the Digital Output internal connection and choose the 16-bit TCPWM.

Choosing a PWM to drive the LED

 

 

Now click on the chain button to jump to the TCPWM.

LED driven from PWM 14 in block 1

Note that it is in block 1, which contains 24 16-bit TCPWMs (block 0 are the 32-bit TCPWMs - that's overkill for this application). Click on the checkbox to enable the peripheral and give it a name - "LED1_PWM".

Enable the TCPWM and give it an alias to make C code easier to write

Notice the configuration panel that appeared when you enabled the peripheral. It has set the Period to the maximum value (32768) and the Compare to 50% of that (16384). Those are good starting conditions - the LED will "glow" at 50% of maximum intensity. You can also see that the PWM_n signal is connected to KIT_LED1, which is a useful confirmation.

Configuration panel showing the period and compare values

The last edit is to set up a source clock, which follows a very similar path to what we just did. Click on the Clock Signal, under Inputs, and choose "8 bit Divider 2 clk". In the pull-down you will see all the other clocks that are set up by the template. You could reuse one of those if you wish to share the resources, but this is a simple application that will only use a fraction of available resources so I am keeping things simple. Click on the chain to jump to the clocks tab. Notice that the clock is enabled for you, with a divider of 1, giving a 72MHz frequency for the counter. That's plenty o' Hertz! Save the changes and exit the configurator.

In the GeneratedSource/cycfg_peripherals.h file you will find these new defines that we will use to start the PWM.

#define LED1_PWM_HWTCPWM1

#define LED1_PWM_NUM 14UL

#define LED1_PWM_MASK (1UL << 14)

In cycfg_peripherals.c there is a configuration struct. All those PWM options are really impressive but I am glad I do not have to set them up by hand!

const cy_stc_tcpwm_pwm_config_t LED1_PWM_config =

{

.pwmMode = CY_TCPWM_PWM_MODE_PWM,

.clockPrescaler = CY_TCPWM_PWM_PRESCALER_DIVBY_1,

.pwmAlignment = CY_TCPWM_PWM_LEFT_ALIGN,

.deadTimeClocks = 0,

.runMode = CY_TCPWM_PWM_CONTINUOUS,

.period0 = 32768,

.period1 = 32768,

.enablePeriodSwap = false,

.compare0 = 16384,

.compare1 = 16384,

.enableCompareSwap = false,

.interruptSources = CY_TCPWM_INT_NONE,

.invertPWMOut = CY_TCPWM_PWM_INVERT_DISABLE,

.invertPWMOutN = CY_TCPWM_PWM_INVERT_DISABLE,

.killMode = CY_TCPWM_PWM_STOP_ON_KILL,

.swapInputMode = LED1_PWM_INPUT_DISABLED & 0x3U,

.swapInput = CY_TCPWM_INPUT_0,

.reloadInputMode = LED1_PWM_INPUT_DISABLED & 0x3U,

.reloadInput = CY_TCPWM_INPUT_0,

.startInputMode = LED1_PWM_INPUT_DISABLED & 0x3U,

.startInput = CY_TCPWM_INPUT_0,

.killInputMode = LED1_PWM_INPUT_DISABLED & 0x3U,

.killInput = CY_TCPWM_INPUT_0,

.countInputMode = LED1_PWM_INPUT_DISABLED & 0x3U,

.countInput = CY_TCPWM_INPUT_1,

};

Add these lines to your main() function (in Source/main.c) before the for loop.

    Cy_TCPWM_PWM_Init(     LED1_PWM_HW, LED1_PWM_NUM, &LED1_PWM_config );

    Cy_TCPWM_TriggerStart( LED1_PWM_HW, LED1_PWM_MASK );

    Cy_TCPWM_PWM_Enable(   LED1_PWM_HW, LED1_PWM_NUM );

Notice that the trigger function works on all 24 TCPWMs in the block, and so we pass a MASK argument, not the individual TCPWM number. LED1_PWM_MASK is just a 1 shifted left by LED1_PWM_NUM and so you can see how to control and synchronize the triggering of individual TCPWMs by ORing the masks.

When you build and program the application into your Pioneer kit the LED should glow quite brightly. This is because a 50% duty cycle is almost as bright as 100% to our eyes. Even your young ones! A household mirror reflects about 50% of the light that hits it and your reflection does not appear especially dim! So play with the duty cycle to reduce the brightness. You can do this by editing the compare value in the configurator or by calling the Cy_TCPWM_PWM_SetCompare0() function to change it your C code (use the Compare0 function because the PWM supports two period and compare values, which we will play with next time). If you have never done this before I think you will be surprised at how low the duty cycle has to be in order to really dim the LED.

I wrote about Zerynth just after the Embedded World show, where they gave a great demo of their Python virtual machine running an IoT demonstration on a PSoC Pioneer kit.

Like all good demos it was, in essence, really simple. They dropped a MikroE Weather Click board (with a Bosch BME280 humidity/pressure/temperature sensor) onto the Pioneer kit, read values over I2C into the PSoC application, which published the weather data to the cloud wirelessly whenever the CapSense button was pressed. Simple right? Yeah, of course it is simple - but only because Cypress has invested decades of R&D effort and millions of dollars into making the most robust and best performing CapSense and wireless solutions on the planet! But the demo is simple and the Zerynth stuff is cool.

Better still, late last week they formally announced the availability of the software in their excellent Zerynth Studio product. So go download it now!!!!

PSoC Pioneer kit running the Zerynth Python VM with a MikroE Arduino Uno shield and Weather Click sensor board

I am very excited about this collaboration. The Zerynth folk are really easy to work with and their excitement about the product is infectious. I think it has something to do with being young and Italian, and having perfect hair! When they are not making innovative products I imagine they just scoot around Pisa, on Vespas, calling out "ciao" to all the pretty people. Well, maybe that's just what I would do?

In case you hadn't figured it our yet, I've run out of things to write... I want to go learn Python and try it out on my Pioneer kit... and so should you... so get downloading and join the millennials by coding in Python. It'll make you feel years younger. And your hair will look good too.

Our friends over at Percepio just won a Best in Show award at Embedded World in Germany. Device Firmware Monitor (DFM) was the winner in the Development Tools category.

Percepio Device Firmware Monitor, the new cloud service for software quality assurance in IoT product organizations, has been awarded Best in Show at Embedded World 2019, in the Development Tools category.DFM is an exciting new cloud service for IoT firmware developers, providing awareness of firmware issues in deployed devices and speeding up resolution. DFM notifies firmware developers within seconds after an error has been detected and provides diagnostic information about the issue via the cloud, including a software trace that visualizes what was going on in the firmware when the error occurred. This makes it far easier to understand the problem, find a solution, and quickly dispatch an over-the-air (OTA) update to correct the problem. DFM integrates Percepio's leading analysis tool Tracealyzer, with AWS IoT connectivity.Percepio Best in Show Diploma

 

Most embedded software is shipped with defects remaining in the code. On average, 5 percent of all bugs introduced during development elude verification efforts and make their way to customers. The IoT trend brings OTA updates as a potential remedy, but developers can't fix bugs they are not aware of. Automatic feedback is needed. Percepio DFM is a ground-breaking new approach, providing awareness and diagnostics that allow IoT developers to leverage the full potential of OTA updates to continuously improve their firmware and customer experience.

Congratulations to Johan, Mike and the rest of the Percepio team! We think DFM is an exciting new product and, pretty soon, we hope to test it out with PSoC 6 and our connectivity devices.

Last time I blogged (is that really a word? what a strange world this is) I wrote about the ModusToolbox device configurator. We set up a pair of pins, one called "LED" and the other "SW" (because, it turns out that I am too lazy to write "Switch" or "Button"). Anyway, I mentioned that the configurator generates the setup code for the pins and that I would explain how that works. So here goes...

At the top of main() there is a function call to "init_cycfg_all()" and that's where the pins get initialized. That function lives in a file called cycfg.c which you can find in the GeneratedSource folder.

You can see here that this folder is actually a link (see the tiny arrow on the folder icon) to the configuration project. That project is the one that contains the design.modus file, which contains all the selections and information from the configurators, and the source files generated when I saved my configuration.

Also in this folder are the configuration files for connectivity, which means the signals that are connected inside the PSoC, platform, which is the power, debug and clock setup code, and pins, which we are most interested in.

 

First, here is the meat of cycfg.c, showing the init_cycfg_all() function.

 

#include "cycfg.h"

void init_cycfg_all()
{
    init_cycfg_pins();
    init_cycfg_platform();
    init_cycfg_connectivity();
}

 

I am sure you'll be flabbergasted to learn that init_cycfg_pins() is in cycfg_pins.c - this is pretty simple stuff (by design - so you can dig in and really understand what the tools are doing for you). What does that function look like? Well, this...

 

void init_cycfg_pins(void)
{
    Cy_GPIO_Pin_Init(SW_PORT, SW_PIN, &SW_config);
    Cy_GPIO_Pin_Init(LED_PORT, LED_PIN, &LED_config);
    Cy_GPIO_Pin_Init(SWO_PORT, SWO_PIN, &SWO_config);
    Cy_GPIO_Pin_Init(SWDIO_PORT, SWDIO_PIN, &SWDIO_config);
    Cy_GPIO_Pin_Init(SWCLK_PORT, SWCLK_PIN, &SWCLK_config);
}

 

The pins are set up with a call to a PDL function that accepts the port address, pin number within the port, and a pointer to a configuration struct. There are five calls - one for LED, one for SW, and three for debugging - SWCLK, SWDIO, and SWO. It's all simple enough, but where do those arguments come from? The ports and pins are defines and they come from cycfg_pins.h. Here is a snippet for SW, the input pin.

 

#define SW_PORT GPIO_PRT0
#define SW_PIN 4U
#define SW_NUM 4U
#define SW_DRIVEMODE CY_GPIO_DM_PULLUP
#define SW_INIT_DRIVESTATE 1
#ifndef ioss_0_port_0_pin_4_HSIOM
#define ioss_0_port_0_pin_4_HSIOM HSIOM_SEL_GPIO
#endif
#define SW_HSIOM ioss_0_port_0_pin_4_HSIOM
#define SW_IRQ ioss_interrupts_gpio_0_IRQn

 

 

I'll go through all of those defines in order. SW_PORT is the crucial macro, it maps to a pointer to the port 0 hardware:

#define GPIO_PRT0                               ((GPIO_PRT_Type*) &GPIO->PRT[0])

I am glad that's generated for me!

Next there is SW_PIN, the pin number. SW_NUM is always the same as the PIN macro - the "NUM" style is used for lots of peripherals (which I'll show you next time) and so we generate two macros, in case you are lazy like me and always want to use one style. it's your choice.

After that we have the drive mode - PULLUP creates an active low button. DRIVESTATE is the initial pin value (so pin reads get the right result before the button is pressed). Then there are some definitions of internal connections, which are unused for software-driven GPIOs. Lastly, there is the IRQ definition, which is useful when you trigger interrupts from the pin and REALLY useful when you trigger interrupts on multiple pins in the same port (another subject for another day).

All that explains where the first two arguments come from. The last one is a C variable and it is back in the cycfg_pins.c file. Here is the configuration struct for SW.

 

const cy_stc_gpio_pin_config_t SW_config =
{
    .outVal = 1,
    .driveMode = CY_GPIO_DM_PULLUP,
    .hsiom = SW_HSIOM,
    .intEdge = CY_GPIO_INTR_DISABLE,
    .intMask = 0UL,
    .vtrip = CY_GPIO_VTRIP_CMOS,
    .slewRate = CY_GPIO_SLEW_FAST,
    .driveSel = CY_GPIO_DRIVE_FULL,
    .vregEn = 0UL,
    .ibufMode = 0UL,
    .vtripSel = 0UL,
    .vrefSel = 0UL,
    .vohSel = 0UL,
};

 

I will not bore you explaining all of that but a few things are intreesting. Firstly, the struct is const. That means it is stored in flash and not copied into RAM on startup. This saves memory but, if you want to change the struct at run-time, you can turn off the const from the device configurator, by un-checking "Store Config in Flash". Note that this option saves RAM, but not flash, because the INITDATA always has to be kept in non-volatile memory.

 

 

Back in the C code you can see that the pin initialization, like the PULLUP and interrupt settings, is all contained in the struct. The end result of all this code is that device initialization is an automated function with PSoC in ModusToolbox but, if you need to over-ride these choices for any reason, it is really easy to make a copy of the generated code, edit it and simply call your new function and not the generated one!

Next time, I'll take you a little deeper into the configurators and set up some more interesting peripherals, and then, if all goes well, I'll introduce you to the wonders of the CapSense configurator and its pal, the CapSense tuner!