AnyCloud Bluetooth Advertising Scanner (Part 9)

Summary

In this series of articles I am building a Bluetooth Low Energy Scanner using the Cypress/Infineon AnyCloud SDK running on a PSoC 6 and CYW43xxx.  In Part 9, I will fix a memory leak, add packet age, and improve the printing.

Story

You might be starting to wonder if this series is ever going to end.  Well, this article and one more.  That is it.

This morning as I was looking at the serial console window I noticed that I had hit the limit of device in the buffer,  OK.  But that it had also crashed, gone, bye bye, so long … the long dark road.  That needs fixing.

I also was curious when I looked at the output, how long ago I had seen the packets/devices.  So I decided that having “age” in the database made sense.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Fix the Memory Leak

I noticed that a while after I started getting the message “ADV Table Max Size” that things would crash.  But Why?  The answer is a memory leak – go figure. I originally thought when I get a new device I would just overwrite the last entry in the table.  But, when I overwrote the adb_database[adb_db_count]. record with a new scan_result and a new list, I left memory that was previously allocated, here is the code:

    if(entry == -1)
    {
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].listCount = 1;
        adb_database[adb_db_count].record = false;
        adb_database[adb_db_count].filter = true;
        adb_database[adb_db_count].numSeen = 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;
        current->count = 1;

        adb_database[adb_db_count].list = current;

        adb_db_count = adb_db_count + 1;
        if(adb_db_count == ADB_MAX_SIZE)
        {
            printf("ADV Table Max Size\n");
            adb_db_count = adb_db_count - 1;
        }
        else
        {    
            adb_db_print(ADB_PRINT_METHOD_BYTES,false,adb_db_count-1);
        }
    }

A cheap fix is to just stop making new entries when the database runs out of room.

    // If there is a new entry and you ran out of space
    if(entry == -1 && adb_db_count >= ADB_MAX_SIZE)
    {
        free(scan_result);
        free(data);
        return;
    }

Add Age

As I mentioned earlier I wanted to keep track of

  1. The last time I had seen a device
  2. When I saw that specific advertising packet

The FreeRTOS has a free running millisecond counter that starts a 0 and counts up to 2^32.  A really cheap way to keep track of time is just to use this counter. To do this the first step is add the time to the database.  Both in the device record and the packet record.

typedef struct {
    uint8_t *data;
    int count;
    TickType_t lastSeen;
    struct adb_adv_data_t *next;
} adb_adv_data_t;

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    bool record;
    bool filter;
    int numSeen;
    int listCount;
    TickType_t lastSeen;
    adb_adv_data_t *list;
} adb_adv_t ;

Update the Printing

I am going to make the output look like this with a new column representing the seconds since I heard the packet.  In the picture below you can see that I heard 00 at 0.0 seconds ago…  Then you can see that I had a recording of device 5 where I have a bunch of packets that I heard back into time.

To do this I just add a time calculation like this:

static void adb_db_printEntry(adb_print_method_t method, int entry, adb_adv_data_t *adv_data)
{
    float time = ((float)xTaskGetTickCount() - (float)(adv_data->lastSeen))/1000;

    printf("%c%c%02d %05d %03d %6.1f ",adb_database[entry].watch?'W':' ',
    adb_database[entry].filter?'F':' ',
    entry,adb_database[entry].numSeen,adb_database[entry].listCount,
    time);

Fix up the Add

The next thing that I need to do is make the “add” function add the time.  The problem is that this function has gotten totally totally out of control.  It turns out that there are x different possibilities

  1. Ignore the packet (because the table is full)
  2. Add a new device & packet
  3. Update the head of the list with a new packet
  4. Insert a new packet at the head of the list
  5. If you are filtering update a duplicated packet count

The code for these branches all looked somewhat similar.  But, which branch to take depended on

  1. If you were “watching” that device
  2. If you were “filtering” that device
  3. If you were “recording”
  4. If you had seen that packet before (aka it was found)

I ended up making a truth table:

Watch Filter Recording Found Action
0 0 0 0 update the head
0 0 0 1 update the head
0 0 1 0 update the head
0 0 1 1 update the head
0 1 0 0 update the head
0 1 0 1 update the head
0 1 1 0 update the head
0 1 1 1 update the head
1 0 0 0 update the head
1 0 0 1 update the head
1 0 1 0 insert at the head
1 0 1 1 insert at the head
1 1 0 0 update the head
1 1 0 1 update the found
1 1 1 0 insert at the head
1 1 1 1 update the found

The case where you

  1. Had no room
  2. Saw a new device

Look like this:

static void adb_db_add(wiced_bt_ble_scan_results_t *scan_result,uint8_t *data)
{

    TickType_t timeSeen = xTaskGetTickCount();

    int entry = adb_db_find(&scan_result->remote_bd_addr);

    // If there is a new entry and you ran out of space
    if(entry == -1 && adb_db_count >= ADB_MAX_SIZE)
    {
        free(scan_result);
        free(data);
        return;
    }
    
    // If it is NOT found && you have room
    if(entry == -1)
    {
        adb_database[adb_db_count] = malloc(sizeof(adb_adv_t));
        adb_database[adb_db_count]->result = scan_result;
        adb_database[adb_db_count]->listCount = 1;
        adb_database[adb_db_count]->watch = false;
        adb_database[adb_db_count]->filter = true;
        adb_database[adb_db_count]->numSeen = 1;
        adb_database[adb_db_count]->lastSeen = timeSeen;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;
        current->numSeen = 1;
        current->lastSeen = timeSeen;

        adb_database[adb_db_count]->list = current;

        adb_db_count = adb_db_count + 1;    
        adb_db_print(ADB_PRINT_METHOD_BYTES,false,adb_db_count-1);

        return; 
    }

At this point in the code you know that you have seen this device before.  If you are filtering you should look in the linked list to see if you can find the specific packet (lines 1-15).

If you look at the truth table above you will see three cases where you should insert at the head of this list.  Those cases are identified with the sprawling if on lines 17-21).  Once you identify that scenario you do the needful.

    adb_adv_data_t *updateItem=0; 

    if(adb_database[entry].filter) // if filtering is on.
    {
        int len = btutil_adv_len(data); // ARH maybe a bug here
        
        for(adb_adv_data_t *list = adb_database[entry].list;list;list = (adb_adv_data_t *)list->next)
        {
            if(memcmp(list->data,data,len) == 0) // Found the data
            {
                updateItem = list;
                break;
            }
        }
    }

    // insert at the head
    if( (adb_database[entry].watch && !adb_database[entry].filter && adb_recording && !updateItem) ||
        (adb_database[entry].watch && !adb_database[entry].filter && adb_recording && updateItem) ||
        (adb_database[entry].watch && adb_database[entry].filter && adb_recording && !updateItem)
    )
    {
        adb_adv_data_t *updateItem = malloc(sizeof(adb_adv_data_t)); // make new data
        updateItem->next = (struct adb_adv_data_t *)adb_database[entry].list;
        updateItem->numSeen = 1;
        updateItem->data = data;
        updateItem->lastSeen = timeSeen;

        adb_database[entry].list = updateItem;
        adb_database[entry].numSeen += 1;
        adb_database[entry].lastSeen = timeSeen;
        adb_database[entry].listCount += 1;
        free(scan_result);
        
        adb_db_print(ADB_PRINT_METHOD_BYTES,false,entry);


        adb_recording_count += 1;
        if(adb_recording_count == ADB_RECORD_MAX)
        {
            adb_recording = false;
            printf("Recording buffer full\n");
        }
        return;
    }

The final case happens when you are just going to update a found packet.

    if(updateItem == 0)
        updateItem = adb_database[entry].list;


    adb_database[entry].numSeen += 1;
    adb_database[entry].lastSeen = timeSeen;

    updateItem->lastSeen = timeSeen;

    int len = btutil_adv_len(data); // ARH maybe a bug here
    if(memcmp(updateItem->data,data,len) == 0)
    {
        updateItem->numSeen += 1;
    }
    else
    {
        updateItem->numSeen = 1;   
    }

    free(updateItem->data);
    updateItem->data = data;
    free(scan_result);

}

In the next article I will add

  1. Sort
  2. Purge

Then I will call it  a day.

Beer Smith Water Volume Calculator

Summary

A discussion of the algorithms to calculate water volume in Beer Smith 3.0 including a spreadsheet showing the calculations.  What does this have to do with IoT?  Nothing.

Story

I am a new brewer. I started at the beginning of COVID in March 2020.  At this point I have only done 26 batches so I am a long way from expert.  For sure there is no danger of BrewExpert.com from me any time soon.  But, I am also an engineer so I like to “know” (and if there are any IoT Expert people still reading, you will already know that)

I have been struggling to understand how much strike and sparge water to use during the making process. I own and use a Grainfather, so I have been using their software, but honestly Im not a huge fan.  Nathan at BrewerDude, my local home brew store, told me that using Beer Smith was the best way to improve my results.  And, as you know figuring things out is more than half the fun for me.  Game on.

Beer Smith 3.0 is a interesting piece of software written by Brad Smith that was made to design and help you implement beer recipes.  The software was clearly built with years of “experience” in making beer and is also clearly a reflection of his workflow.  The tool is seems to have a substantial amount of empirical knowledge built into the tool – Ohm’s laws for beer?

Now to the problem.  I started trying to reconcile the water volume information coming out of Beer Smith with what was coming out of the Grainfather software.  The numbers didn’t really add up and there were a bunch of things which I didn’t really know. Google here we come. Turns out there are a boatload hits that are some variant of “how do I match what the Grainfather and BeerSmith think for water volumes”.  I am sure that there is a beautiful document in the Beer Smith documentation library which I haven’t found which explains the answer to the question of how the water volumes are calculated in Beer Smith, but I couldn’t find it.

So I decided to reverse engineer the calculations, which I mostly have done (with a few issues).  They are all in a spreadsheet which you can download from the IoT Expert Github respository which you can find at https://github.com/iotexpert/Beer.git That repository contains a spreadsheet called “beer-smith-water.xlsx” which has my version of the formulas which seem to match BeerSmith 3.1.08

I am quite sure that this water volume issue is obvious to everyone, except me.  But, as I said, I’m only 26 batches into this brewing adventure.  For the rest of this article I will focus only on the parameters that impact water volume in Beer Smith.

First I will walk through the screens that impact water then I will show the “Vols” tab and my spreadsheet which mimics the calculations.

Getting Started

I will use the recipe “Abbey of the Blonds” from BrewerDude and my setup which is a Grainfather G30 and a Grainfather Conical Fermenter.  Here is a picture of the front cover of the book that came with the recipe.  Notice my handwritten notes that are the strike and sparge water… in both metric and imperial… and my note that the water values that I used to get a good result.

Before you can really look at the water volume numbers you need to do several things:

  1. Enter the recipe (which I copied from the nice document that came from BrewerDude along with the kit)
  2. Add your equipment (in the picture you can see that I added Grainfather G30 110V ARH).  “ARH” are my initials and is my copy of the default configuration.
  3. Add your mash profile (in the picture you can see that I picked Single Infusion Medium Body, Batch Sparge)
  4. Set the boil time to 90 minutes (which over-rides the default of this equipment of 60 minutes)
  5. Notice the Batch Size of 5.5 gallons (which was copied from the equipment profile)

In the picture you can see that I changed the fields in the middle right to display the three water volume numbers that I am interested in, specifically:

  • Tot Mash Water
  • Sparge Water
  • Total Volume

I do not believe that BH Efficiency impacts the water volume calculations.

Batch Size & A GUI Comment

The starting value for Batch Size is copied from Equipment Setup configuration.  This number was intensely confusing to me for a good while until I got it sorted out that this is the total volume of cold wort going into the fermenter after the mash and boil.  One thing that you should watch for is that this value is linked onto multiple menus/screens and a change in one place will effectively change it multiple places.  When you change this value it appears to propagate throughout your recipe but it does not change the original source equipment profile.

Equipment Setup

As I stated above, when you create the recipe you effectively COPY the equipment profile into your recipe.  In other words the values in this tab become the default values for your recipe when you configure this equipment profile to be used for your recipe.

In the equipment profile the following values impact water:

  • Batch Volume – How much wort you want to go into the fermenter
  • Fermenter Loss – Dead space at bottom of the fermenter i.e. below the line where you bottle from
  • Mash Tun Volume – Used as a check to make sure you don’t overflow during the mash.  This doesn’t change the calculations, just a warning.
  • Mash Tun Weight – No impact on water? (pretty sure)
  • Mash Tun Specific Heat – No impact on water? (pretty sure)
  • Boil Time – The default length of time to boil.  This is copied into your reciepe.  This parameter is used in the boil off calculation
  • Boil Off – The input variable to the total boil off calculation
  • Use boil as an hourly rate (if you click this boil off will vary with the boil time)
  • Total Boil Off: Calculated boil off = Batch Size * boil time / 60 * Boil Off
  • Recoverable Mash Deadspace – volume of water below the false bottom which WILL be part of the final wort
  • Mash Deadspace Losses – the volume of water below the false bottom which willl be LOST and not part of the final wort
  • Top Up Water for Kettle: How much water you add pre-boil
  • Post Boil Vol: This is a calculated estimate based on the input parameters (more details later)
  • Cooling Shrinkage: how much volume you loose as the wort cools
  • Loss to Trub and Chiller – what it says
  • Top Up Water – How much water you add into the fermenter

You will find that most of these values are copied onto the “Vols” screen later on.

Mash Profile

When you choose the mash profile it is the major input to the strike water calculations.

On the Mash screen the parameters:

  1. Grain Weight Basis – used for the calculation of strike water (derived from your inputs on the recipe)
  2. Mash In – will take you to the next screen where the strike water is calculated

I was lost trying to figure out where the strike water came from for quite a while until I realized that you could change the values on the “Mash In” step screen.  To do this click on it and press “Edit Step” (where the water volume action happens).  Really what is happening here is that you can create multiple steps in your mash process.  Each step can/will impact the water used by that step.  When you configure your reciepe to use a mash profile, you are essentially copying those steps into your recipe and they drive the values for strike water in your recipe.

As I work to write this article I notice for the first time “Batch Sparge using batches that fill 90%” but I have no idea what that impacts.

The Mash Step screen calculates the mash water for this step.  The key metric is the Water/Grain Ratio which is used to calculate the “Water to add”.  Where did the 1.250 qt/lb come from?  I suspect that this is empirical.  Notice that the water to add does NOT include the Mash Tun Deadspace Addition.  This number was copied from the Equipment profile (which recall was 0.92 gallon = 3.68qt).  Im sure that there was a good reason to add a place to enter this value here, but damn if I can figure it out.  I’m also sure there was some good reason to change units, but Im also not sure why, but probably convenience.

The Advanced Options

The advanced options screen (which can be accessed from the preferences button) has two numbers which are relevant to the water calculations

  1. Grain Absorption (how much water is absorbed by the grain) = 0.96 Fluid Oz per Oz of Grain
  2. Grain Volume (how much physical space is taken by the wet grain) = 0.6520 l/kg

Im quite sure that both of these numbers were arrived at empirically.  These numbers are used in the upcoming “Vols” calculations.

The “Vols” Tab

This tab is where the action happens.  You can see and edit most of what you might want from a water volume point of view.  This screen is broken up into four sections which I will walk through one by one.

  1. Water Needed
  2. Mash
  3. Boil and Fermentation
  4. Fermentation/Bottling

The first thing you should know on the “Vols” tab is that the Total Volume = Tot Mash Water + Sparge Vol and is calculated for you.

The next section is Mash

The Mash Grain Wt (weight) comes from your recipe.

Grain Absorption = Mash Grain Wt * Grain Absorption (from the advanced options)

The Tot Mash Water comes from the Mash Profile which you set earlier plus the Mash Tun Addition.  In this specific case it was calculated with

  • 10.88 pounds * 1.25 qts/pound * 1 gallon/4 quarts = 3.4 Gallons + 0.92 Gallons = 4.32 Gallons

The Tun Deadspace is a parameter for you to enter and is the unrecoverable amount of fluid in the mash tun (things below the pickup in the bottom of the Grainfather)

The Mash Volume Needed = Total Mash Water + Volume of the Wet Grain (which isn’t shown on this screen but is on my spreadsheet)

The Volume of the Wet Grain = Grain weight * gallons/pound  Remember from the advanced tab which had Grain Volume in litres/pound.  This is how much space is taken up inside of your mash tun for wet grain.

Water Available from the Mash = Total Mash Waster – Grain Absorption (this is how much wort can move to the next step)

The Sparge Vol comes from a long chain of calculations on the next sections…

Here is the section of my spreadsheet which represents these calculations.

Now I am going to skip the Boil and Fermentation section and come back because the Fermentation/Bottling section uses data from the this section as an Input.

Top Up Water is how much water you add to the boiled wort as you add it into the Fermenter.  I am not sure why you would do this, perhaps to lower the gravity?

The Batch Size is a parameter that came from the recipe page and is all over this software.  This value is how much wort you are going to put into the fermenter.

The bottling/fermentation loss is how much you loose at the bottom of your fermenter and by taking samples or draining crap during the fermentation process.

I don’t know what “Starter Size Used” is, but I believe that it was put there for a Yeast starter (I have been using Propper Starter).  This would add 32 Ozs (aren’t the units fun here) or 0.94 of a liter.  On a semi-related note I wonder why they addition of Wort doesn’t change the gravity enough to add into their calculations … I suppose adding 16oz at 1.040 isn’t material.  (late edit: I did the math and adding 16oz of 1.040 to a 5.5 gallon batch of 1.060 lower the gravity by 0.04%)

Here is the Fermentation section from my spreadsheet.  Notice that in the picture there are two yellow fields for you to override the calculations from that step.

Back to the  “Boil and Fermentation” section.

Kettle Top Up is how much water you add before you boil after the mash (I have no idea why you would do this)

The best way to think about the rest of this is to

  1. Remember that we are trying to back calculate Sparge Water
  2. Start from the bottom and work back to the top

Trub loss is what it say it is.  With this you can now calculate a value which isn’t shown on this screen.  I call that value “Post Cool Volume”

Post Cool Volume = Batch Size + Trub Loss – Kettle Top Up

In order to achieve the “Post Cool Volume” you need to increase the work volume by the amount of Cooling Shrinkage.

Pre Cool Volume = Post Boil Volume / (1-Pct Cool Loss)

Cooling Shrinkage = Pre Cool Vol – Post Cool Volume

I suppose that I was surprised that the number was anything other than 0 as I thought that liquids dont change their volume very much.  Shows what an electrical engineer knows eh.

Est Pre-Boil Vol = Pre Cool Volume / ( 1 – (Evaporation Rate * Boil Time / 60 Minutes))

With that you can now calculate Boil Off = Pre  boil – Post Boil

Finally Sparge Volume = Est Pre Boil – Kettle Top Up + Grain Absorption + Tun Deadspace – Tot Mash Water

Here is my spreadsheet:

Final Thoughts

I have not answered (at least) the following questions

  1. Where did the Grainfather numbers come from?
  2. Given (1) why is there a difference between Beer Smith and Grainfather
  3. What other calculators are out there?
  4. What is “Batch Sparge Using Batches that fill %” used for?

I am pretty sure that the right thing for me to do next is to design a set of experiments that will arrive at the configuration parameters that best match my setup.  I think that it would be useful to describe this procedure.

There may very well be an error in here.  In fact I would say with certainty that I made some error.  If you have made it this far and you spotted the error I would very much like to know what it is so that I can fix it.

AnyCloud Bluetooth Advertising Scanner (Part 8)

Summary

In this series of articles I am building a Bluetooth Low Energy Scanner using the Cypress/Infineon AnyCloud SDK running on a PSoC 6 and CYW43xxx.  In Part 8 I will turn on the ability to filter duplicate advertising packets.

Story

In the previous article I added the ability to record advertising packets.  The problem is, of course, that many devices are blasting out advertising packets, which will quickly overwhelm you.  I suppose more importantly it will overwhelm the packet buffer.  Most of the device are just advertising their presence, so they send the same data over and over.  Some devices alternate between a small number of different advertising packets, e.g. an iBeacon then and Eddystone beacon.

The way that the filter will work is that I will update the “add” function to search through all of the packets that device has launched, then if I have seen the packet before (Ill use a memcmp) then I will just keep a count of how many times I have see that packet.

The other thing that needs to happen is for me to add a “filter” command so that I can turn on packet filtering on a device by device basis.

And I need to fix the printing to use the new filtered packet database.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Update the Data Structures

In order to do the filters, and keep track of the data I will add

  1. A “count” field to the packet data structure
  2. A “filter” boolean field to the device data structure.
typedef struct {
    uint8_t *data;
    int count;
    struct adb_adv_data_t *next;
} adb_adv_data_t;

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    bool record;
    bool filter;
    int numSeen;
    int listCount;
    adb_adv_data_t *list;
} adb_adv_t ;

Update the Add Function

In the function “static void adb_db_add(wiced_bt_ble_scan_results_t *scan_result,uint8_t *data)” which is called every time I see a new advertising packet I will need to handle four different cases:

  1. The first time you see a device
  2. A device you have seen AND you are recording AND have the filtering turned on
  3. A device you are recording but not filtering
  4. A device you have seen, but are not recording or filter.

In the first case, a device you haven’t seen before, you need to

  1. Automatically turn on the filtering
  2. Initialize the counts to 1
  3. Do the other initialization (as before)
    // If it is NOT found
    if(entry == -1)
    {
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].listCount = 1;
        adb_database[adb_db_count].record = false;
        adb_database[adb_db_count].filter = true;
        adb_database[adb_db_count].numSeen = 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;
        current->count = 1;

In the case where you have

  1. See the device before
  2. You have room in the record buffer
  3. And you are in record mode

You will then decide if you are filtering.

Then you will iterate through all of the packets and compare the data to the data you just received.  If there is a match then you update the count, free the duplicate data and return.

    else if(adb_database[entry].record && adb_recording_count<ADB_RECORD_MAX && adb_recording)
    {
        adb_database[entry].numSeen += 1;

        if(adb_database[entry].filter) // if filtering is on.
        {
            int len = btutil_adv_len(data); 
            for(adb_adv_data_t *list = adb_database[entry].list;list;list = (adb_adv_data_t *)list->next)
            {
                if(memcmp(list->data,data,len) == 0) // Found the data
                {
                    list->count += 1;
                    printf("Count = %d\n",list->count);
                    free(data);
                    free(scan_result);
                    return;
                }
            }
        }

If you have not see the data before, then you need to add it to the linked list.

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = (struct adb_adv_data_t *)adb_database[entry].list;
        current->data = data;
        current->count = 1;

If you are not recording and not filtering, just increment counts.

    else
    {
        adb_database[entry].numSeen += 1;
        adb_database[entry].list->count += 1;

Add a “filter” Command

I want the ability for a user to “filter all” or “filter clear” or “filter #” – just like we did with watch.  So, add the #defines and new function to advDatabase.h

#define ADB_FILTER_ALL -1
#define ADB_FILTER_CLEAR -2
void adb_filter(int entry);

Then add the new filter command in advDatabase.c

typedef enum {
    ADB_ADD,
    ADB_PRINT_RAW,
    ADB_PRINT_DECODE,
    ADB_WATCH,
    ADB_ERASE,
    ADB_RECORD,
    ADB_FILTER,
} adb_cmd_t;

I will use the adb_queueCmd function that I created in the last article.

inline void adb_filter(int entry) { adb_queueCmd(ADB_FILTER,(void*)entry,(void *)0); }

The filter command has three cases

  1. All – turn the bool for all on
  2. Clear – turn the bool for all off
  3. Just a specific number – toggle that specific number
static void adb_db_filter(int entry)
{
    if(entry == ADB_FILTER_ALL)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].filter = true;
        }
        return;
    }

    if(entry == ADB_FILTER_CLEAR)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].filter = false;
        }
        return;
    }

    if(entry > adb_db_count-1 || entry < ADB_WATCH_CLEAR)
    {
        printf("Record doesnt exist: %d\n",entry);
        return;      
    }
    adb_database[entry].filter = !adb_database[entry].filter; 

}

And you need to fix up the main command processor

                case ADB_FILTER:
                    adb_db_filter((int)msg.data0);
                break;

Finally add the command to the usrcmd.c

static int usrcmd_filter(int argc, char **argv)
{

    if(argc == 2 && !strcmp(argv[1],"all"))
    {
        adb_filter(ADB_FILTER_ALL); // all
        return 0;
    }


    if(argc == 2 && !strcmp(argv[1],"clear"))
    {
        adb_filter(ADB_FILTER_CLEAR);
        return 0;
    }

    if(argc == 2)
    {
        int i;
        sscanf(argv[1],"%d",&i);
        adb_filter(i);
        return 0;
    }

    return 0;
}

Fix the Printing

Now we nee to fix the printing.  I want to add an indicate of the filtering to the output.  Remember from the previous article I indicated “Watch” with a “*”.  When I looked at it, I decided that I should indicate filter with an “F” and watch with a “W”.  So I fix that.

        printf("%c%c%02d %05d %03d MAC: ",adb_database[i].record?'W':' ',
            adb_database[i].filter?'F':' ',
            i,adb_database[i].numSeen,adb_database[i].listCount);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        switch(method)
        {

Then I test the “filter” command

Fix the Printing Part (2)

As I noodled on how to change the printing I decide that it would be nice to sometimes print out only one packet e.g. no history and sometimes print them all out e.g. history.  So, I add a new parameter to the function called “history”

static void adb_db_print(adb_print_method_t method,bool history,int entry)

As I looked at the printing code, I decided that it would be better to have a new function to print only one entry.  I suppose that I could have left the code inline, but I thought that intent was clearer.

static void adb_db_printEntry(adb_print_method_t method, int entry, adb_adv_data_t *adv_data)
{
    printf("%c%c%02d %05d %03d MAC: ",adb_database[entry].record?'W':' ',
    adb_database[entry].filter?'F':' ',
    entry,adb_database[entry].numSeen,adb_database[entry].listCount);
    btutil_printBDaddress(adb_database[entry].result->remote_bd_addr);

    switch(method)
    {
    
    case ADB_PRINT_METHOD_BYTES:
        printf(" Data: ");
        btutil_adv_printPacketBytes(adv_data->data);
    break;

    case ADB_PRINT_METHOD_DECODE:
        printf("\n");
        btutil_adv_printPacketDecode(adv_data->data);
    break;
    } 
    printf("\n");

}

With the new function in place I now need to update the print function to call the new entry function.  Printing the history is just a matter of iterating through the linked list.

static void adb_db_print(adb_print_method_t method,bool history,int entry)
{
    int start,end;
 
    if(entry < 0)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry+1;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<end;i++)
    {
        if(history) // then iterate through the linked list print all of the packets
        {
            for(adb_adv_data_t *list = adb_database[i].list;list;list = (adb_adv_data_t *)list->next)
            {
                adb_db_printEntry(method,i,list);    
            }
        }
        else // Just print the first packet in the list
            adb_db_printEntry(method,i,adb_database[i].list);
    }
}

Now, I need to update all of the calls to adb_db_print to have the new history parameter.  First, I made the decision that when you “print” from the command line that you are interested in the history.

                case ADB_PRINT_RAW:
                    adb_db_print(ADB_PRINT_METHOD_BYTES,true,(int)msg.data0);
                break;
                case ADB_PRINT_DECODE:
                    adb_db_print(ADB_PRINT_METHOD_DECODE,true,(int)msg.data0);
                break;

But when you are printing out the packet for a new device don’t print out the history

            adb_db_print(ADB_PRINT_METHOD_BYTES,false,adb_db_count-1);

Program and Test

After I program my development kit, I start by typing “watch all”.  Very quickly, at my house, you can see that a bunch of devices are discovered.  You can see that all of these have a “W” (meaning that I am watching them) and an “F” meaning they are filtering out duplicates.  Then I type “record” to turn on recording. After a minute I turn off recording then do the print you can see below.

A couple of things to notice.

  1. Device #4 (which I highlighted) appears to be sending out a pattern of alternating packets.  See that I have heard 3335 packets yet there are only two in the buffer
  2. You can see device 11 seems to be sending out 16 different packets.  Why?  I don’t know.

But we can “decode 11” to try to figure it out.  You can see that it is advertising manufactures specific data with Apple’s UUID which I happen to know is 0x004C.  But why?  I don’t know.

I really want to move onto a new series of articles… but there are two functions which I will add to the program.  Stay tuned for what they do.

AnyCloud Bluetooth Advertising Scanner (Part 7)

Summary

In this series of articles I am building a Bluetooth Low Energy Scanner using the Cypress/Infineon AnyCloud SDK running on a PSoC 6 and CYW43xxx.  In Part 7 I will add the ability to record BLE ADV packets.

Story

If you have been reading along, at this point we have built a BLE scanner that can see Bluetooth devices that are advertising.  My scanner has a command line and you can print out the most recent data.  Even better, we built a decoder that allows you to better understand the data.

Now I want to add the ability to record more than one advertising packet per device.  To that end I will add three commands:

  • watch – Mark a device as one that needs to have the advertising data recorded.  You can type “watch 12” or you can say “watch all” or you can say “watch clear”
  • record – Turn on recording of “watched” devices.  When you type record it will toggle the recording state between On and Off.
  • erase – clear the record buffer of all but the most recent packet.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Update the advDatabase Interface

The first thing I realized as I went to add a new command was that I was typing the exact same code over and over for the public interface.  The code looked like this:

void adb_watch(int entry)
{
    adb_cmdMsg_t msg;
    msg.cmd = ADB_WATCH;
    msg.data0 = (void *)entry;
    xQueueSend(adb_cmdQueue,&msg,0); // If the queue is full... oh well

}

So I created this:

static void adb_queueCmd(adb_cmd_t cmd,void *data0, void *data1)
{
    adb_cmdMsg_t msg;
    msg.cmd = cmd;
    msg.data0 = data0;
    msg.data1 = data1;
    xQueueSend(adb_cmdQueue,&msg,0); // If you loose an adv packet it is OK...
}

Then did this to eliminate the duplication.

inline void adb_addAdv(wiced_bt_ble_scan_results_t *scan_result,void *data) { adb_queueCmd(ADB_ADD,(void *)scan_result,(void *)data);}
inline void adb_print(int entry) { adb_queueCmd(ADB_PRINT_RAW,(void *)entry,(void *)0); }
inline void adb_decode(int entry) { adb_queueCmd(ADB_PRINT_DECODE,(void*)entry,(void *)0); }

Redo the Database

If you recall from previous posts, my advertising database was just

  1. An Array of structures
  2. Each structure contained the mac address and…
  3. A pointer to a malloc’d copy of the advertising data
typedef struct {
    wiced_bt_ble_scan_results_t *result;
    uint8_t *data;
} adb_adv_t ;

#define ADB_MAX_SIZE (40)
adb_adv_t adb_database[ADB_MAX_SIZE];

Here is a picture of the datastructure

 

Now what I want to do is make the “data” pointer to be a pointer to a linked list of data.  Here is the new definition.

typedef struct {
    uint8_t *data;
    struct adb_adv_data_t *next;
} adb_adv_data_t;

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    int listCount;
    bool record;
    int numSeen;
    adb_adv_data_t *list;
} adb_adv_t ;

The new data structure looks like this

I wanted to limit the number advertising packets that can be stored so I don’t run out of memory.  I am not actually sure how many can be stored, but I suppose a bunch as the chip has 1MB of RAM.  But, I pick 100 which seems like enough to start out with.  I create two variables

  1. A counter for the number of advertising packets that are currently saved
  2. A recording state (are you saving or not)
#define ADB_RECORD_MAX (100)
static int adb_recording_count = 0;
static bool adb_recording = false;

Update the Printing

You probably noticed that I declared two new members of the adb_adv_t structure, specifically numSeen and listCount, which I would like to print out.   I also wanted a visual indication that I am “watching” a device.  The columns are now:

  1. A “*” to indicate that a device is being watched
  2. The device #
  3. The number of packets that I have seen in total from that device
  4. The number of recorded packets for that device
  5. The MAC address
  6. The raw bytes

To do implement this a simple change is made to the print function:

    for(int i=start;i<end;i++)
    {
    
        printf("%s%02d %05d %03d MAC: ",adb_database[i].record?"*":" ",i,adb_database[i].numSeen,adb_database[i].listCount);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        switch(method)
        {
        
        case ADB_PRINT_METHOD_BYTES:
            printf(" Data: ");
            btutil_adv_printPacketBytes(adb_database[i].list->data);
        break;

        case ADB_PRINT_METHOD_DECODE:
            printf("\n");
            btutil_adv_printPacketDecode(adb_database[i].list->data);
        break;
        } 
        printf("\n");
    }

Update the Add

Now that we have all of the infrastructure in place we need to update the function that saves advertising data.  When an advertising packet comes in you have three situations to consider:

  1. You have never seen the device before
  2. You have seen the device before and you are “watching” it
  3. You have see the device but you are not watching it

In the case where you have never seen the device you need to

  1. Save the scan result
  2. Set the listCount to 1 (you only have one datapoint)
  3. Turn off recording (start with the recording off)
  4. Set the total numSeen to 1 as this is the first packet you have seen
  5. Allocate some memory for the advertising linked list structure
  6. Terminate the linked list
  7. Save the advertising data
  8. Increment the database count (up to the max)
  9. Print out the packet you just saw
    if(entry == -1)
    {
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].listCount = 1;
        adb_database[adb_db_count].record = false;
        adb_database[adb_db_count].numSeen = 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;

        adb_database[adb_db_count].list = current;

        adb_db_count = adb_db_count + 1;
        if(adb_db_count == ADB_MAX_SIZE)
        {
            printf("ADV Table Max Size\n");
            adb_db_count = adb_db_count - 1;
        }
        else
        {    
            adb_db_print(ADB_PRINT_METHOD_BYTES,adb_db_count-1);
        }
    }

In the case where you have

  1. Seen the device before
  2. You are recording that device
  3. There is room left in the recording buffer

Then you will

  1. Increment number seen
  2. Create memory for the new entry in the linked list
  3. Attach the tail of the linked list to your new entry (you will insert at the front of the list)
  4. Save the data
  5. Increment the number of saved entries
  6. Insert your new packet at the head of the list
  7. Printout the packet
  8. Increment the record count (the total number of packets in the recording buffer)
  9. Then potentially stop recording if you have gotten to the max size.
    else if(adb_database[entry].record && adb_recording_count<ADB_RECORD_MAX && adb_recording)
    {
        adb_database[entry].numSeen += 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = (struct adb_adv_data_t *)adb_database[entry].list;
        current->data = data;
        adb_database[entry].listCount += 1;
        adb_database[entry].list = current;

        adb_db_print(ADB_PRINT_METHOD_BYTES,entry);

        adb_recording_count += 1;
        if(adb_recording_count == ADB_RECORD_MAX)
        {
            adb_recording = false;
            printf("Recording buffer full\n");
        }
    }

In the case where you have seen the device before, but you are not recoding then you will

  1. Update the numSeen
  2. Erase the old packet data
  3. Save the new packet
  4. Erase the “result” (you already have it saved)
    else
    {
        adb_database[entry].numSeen += 1;
        free(adb_database[entry].list->data);
        adb_database[entry].list->data = data;
        free(scan_result);
    }

Add a Watch Command

The watch function is pretty simple.  It just needs to either mark the “record” boolean as true or false.  When I decided to implement this function I decided to make positive numbers be the entry in the table.  But, I also wanted to be able to “watch all” and “watch clear”.  So, I used negative numbers for those two special meanings.  I used a #define in advDatabase.h to define those values.

#define ADB_WATCH_ALL -1
#define ADB_WATCH_CLEAR -2

The function is then pretty simple

  1. If it is watch all… then iterate through the database and turn them on
  2. If it is watch clear … then iterate through the database and turn them off
  3. Otherwise make sure that it is a legal number and toggle it.
static void adb_db_watch(int entry)
{
    if(entry == ADB_WATCH_ALL)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].record = true;
        }
        return;
    }

    if(entry == ADB_WATCH_CLEAR)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].record = false;
        }
        return;
    }

    if(entry > adb_db_count-1 || entry < ADB_WATCH_CLEAR)
    {
        printf("Record doesnt exist: %d\n",entry);
        return;      
    }
    adb_database[entry].record = !adb_database[entry].record; 

}

Once I have the infrastructure in place, I then add the watch command to usrcmd.c

static int usrcmd_watch(int argc, char **argv)
{

    if(argc == 2 && !strcmp(argv[1],"all"))
    {
        adb_watch(ADB_WATCH_ALL); // all
        return 0;
    }


    if(argc == 2 && !strcmp(argv[1],"clear"))
    {
        adb_watch(ADB_WATCH_CLEAR);
        return 0;
    }

    if(argc == 2)
    {
        int i;
        sscanf(argv[1],"%d",&i);
        adb_watch(i);
        return 0;
    }

    return 0;
}

Add a Record Command

The record command simply turns on the global bool to either true or false and prints out the number of spaces free in the record “buffer”

                case ADB_RECORD:
                    adb_recording = !adb_recording;
                    printf("Record %s Buffer Entries Free=%d\n",adb_recording?"ON":"OFF",
                        ADB_RECORD_MAX-adb_recording_count);
                break;

And the change to usrcmd.c is also simple.

// record = toggles
static int usrcmd_record(int argc, char **argv)
{
    if(argc == 1)
    {
        adb_record(-1);
        return 0;
    }
    return 0;
}

Add an Erase Command

The erase function is like “watch”, as I overload the “entry” to have an ALL which is setup in advDatabase.h

#define ADB_ERASE_ALL -1

The erase is a bit more complicated than the watch.  When you receive a erase command you will either erase them all by iterating over the whole dates, or just erase one.

                case ADB_ERASE:
                    if((int)msg.data0 == ADB_ERASE_ALL)
                    {
                        for(int i=0;i<adb_db_count;i++)
                        {
                            adb_eraseEntry(i);
                        }
                    }
                    else
                        adb_eraseEntry((int)msg.data0);

                    printf("Record Buffer Free %d\n",ADB_RECORD_MAX-adb_recording_count);
                break;

The individual eraseEntry function checks to make sure that you have a legal “entry”.  Then it follows the linked list “freeing” the data structures.

static void adb_eraseEntry(int entry)
{
    if(entry > adb_db_count-1 || entry<0)
    {
        printf("Erase Entry Not Found %d\n",entry);
        return;
    }

    adb_adv_data_t *ptr;
    ptr = (adb_adv_data_t *)adb_database[entry].list->next;
    adb_database[entry].list->next = 0;
    while(ptr)
    {
        adb_adv_data_t *next;
        next = (adb_adv_data_t *)ptr->next;
        free(ptr->data);
        free(ptr);
        adb_database[entry].listCount -= 1;
        adb_recording_count -= 1;
        ptr = next;
    }
}

And, of course, you need to add the command to usrcmd.c

// erase
// erase #
static int usrcmd_erase(int argc, char **argv)
{
    if(argc > 2)
    {
        return 0;
    }

    if(argc == 1)
    {
        adb_erase(ADB_ERASE_ALL);
        return 0;
    }

    int i;
    sscanf(argv[1],"%d",&i);
    adb_erase(i);
    return 0;    

}

Now when you build and program the kit you can turn on/off recording and erase and….

In the next post I will add

  1. Smarter printing
  2. A “filter” to eliminate duplicate advertising packets

AnyCloud Bluetooth Advertising Scanner (Part 6)

Summary

In part 6 of this series I will update the AnyCloud BLE Advertising Scanner to decode advertising packets into a more human readable textual output

Story

We are now 6 (or maybe 7 depending on how you count) articles into this series and we are still looking at raw bytes.  I have gotten to where I am pretty good at understanding those bytes, but that is now way to roll.  You might remember from the article on the IoT Expert Bluetooth Utility library that there were a some interesting functions defined in the header.  Here it is:

wiced_bool_t btutil_isEddystone(uint8_t *data);
wiced_bool_t btutil_is_iBeacon(uint8_t *data);
wiced_bool_t btutil_isCypress(uint8_t *data);

int btutil_adv_len(uint8_t *packet);
void btutil_adv_printPacketDecode(uint8_t *packet);
void btutil_adv_printPacketBytes(uint8_t *packet);

Lets transform our  project from part 6 to use these functions.  In this article I will

  • Redo the print bytes (to be smarter) functionality and to use the built in function
  • Rework the logic for the “print” command implementation
  • Add a new command “decode” which will run the decode function

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Replace two code blocks with function calls

Do you remember this block of code?  It print’s out the 6-bytes of the Bluetooth Address.

    for(int i=0;i<BD_ADDR_LEN;i++)
    {
        printf("%02X:",adb_database[entry].result->remote_bd_addr[i]);
    }

You might have noticed that this function already exists in the BT Utility Library.  Use it.

    btutil_printBDaddress(adb_database[entry].result->remote_bd_addr);

Then you remember this block of code which iterates through and advertising packet and prints out the raw bytes?

// Print the RAW Data of the ADV Packet
    printf(" Data: ");
    int i=0;
    while(adb_database[entry].data[i])
    {
        for(int j=0;j<adb_database[entry].data[i];j++)
        {
            printf("%02X ",adb_database[entry].data[i+1+j]);
        }
        i = i + adb_database[entry].data[i]+1;
    }

Well, it exists in the library as well.  Use it.

btutil_adv_printPacketBytes(adb_database[entry].data);

Fix the “print” Command

In the previous implementation I had two functions for “print”.  The first one printed one entry and the second one printed the whole table.  I decided that I didnt really like this logic, so I compressed those two functions into one function.  Specifically, it take a number “entry”.  If that number is -1 then it will print the whole table.

static void adb_db_printRawPacket(int entry)
{
    int start,end;
 
    if(entry <= -1)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<=end;i++)
    {
    
        printf("%02d MAC: ",i);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        printf(" Data: ");
        btutil_adv_printPacketBytes(adb_database[i].data);

        printf("\n");
    }
}

Add a new “decode” Command

The next thing to do is to add a function to print out decoded packets (or the whole table).  So I wrote this:

static void adb_printDecodePacket(int entry)
{
    int start,end;
 
    if(entry == -1)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<=end;i++)
    {

        printf("%02d MAC: ",i);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        printf("\n");
        btutil_adv_printPacketDecode(adb_database[i].data);
        printf("\n");
    }
}

After finishing that block of code, I realized I had implemented almost exactly the same functionality which two different functions.  So, I decided to redo this by doing this.  Notice that it take in a adb_print_method, in other words raw bytes or decoded packet.

typedef enum {
    ADB_PRINT_METHOD_BYTES,
    ADB_PRINT_METHOD_DECODE,
} adb_print_method_t;

static void adb_db_print(adb_print_method_t method,int entry)
{
    int start,end;
 
    if(entry < 0)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<=end;i++)
    {
    
        printf("%02d MAC: ",i);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        switch(method)
        {
        
        case ADB_PRINT_METHOD_BYTES:
            printf(" Data: ");
            btutil_adv_printPacketBytes(adb_database[i].data);
        break;

        case ADB_PRINT_METHOD_DECODE:
            printf("\n");
            btutil_adv_printPacketDecode(adb_database[i].data);
        break;
        } 
        printf("\n");
    }
}

I don’t show it here, but after changing this I had to fix up the function calls in several places in the advDatabase.

Add a new command to print decode packets

Now that I have a new method to print packets, I add a command to the database to allow the user to call it:

typedef enum {
    ADB_ADD,
    ADB_PRINT_RAW,
    ADB_PRINT_DECODE,
} adb_cmd_t;

Then in the queue loop:

switch(msg.cmd)
            {
                case ADB_ADD:
                    scan_result = (wiced_bt_ble_scan_results_t *)msg.data0;
                    data = (uint8_t *)msg.data1;
                    adb_db_add(scan_result,data);
                break;
                case ADB_PRINT_RAW:
                    adb_db_print(ADB_PRINT_METHOD_BYTES,(int)msg.data0);
                break;
                case ADB_PRINT_DECODE:
                    adb_db_print(ADB_PRINT_METHOD_DECODE,(int)msg.data0);
                break;
            }

Then the actual function

void adb_printDecode(int entry)
{
    adb_cmdMsg_t msg;
    msg.cmd = ADB_PRINT_DECODE;
    msg.data0 = (void *)entry;
    xQueueSend(adb_cmdQueue,&msg,0); // If the queue is full... oh well
}

Then add it to advDatabase.h

void adb_printDecode(int entry);

Finally to the usercmd.c

static int usrcmd_printDecode(int argc, char **argv)
{
    if(argc == 1)
    {
        adb_printDecode(-1);
    }

    if(argc == 2)
    {
        int val;
        sscanf(argv[1],"%d",&val);
        adb_printDecode(val);
    }
    return 0;
}

Program and Test

When I actually program the scanner you can see that I can print out 1 item.  OR I can decode one item.  Notice that one contains 3 fields

  • flags
  • Tx Power Level
  • Manufacturers data.  Apparently an Apple something or the other

And I can print the whole table

Or decode the whole table.

 

AnyCloud Bluetooth Advertising Scanner (Part 5)

Summary

In this article I will add a new task to the AnyCloud BLE Advertising Scanning application which will save the advertising data into a database.

Story

There is still a boatload of mostly unintelligible advertising data coming ripping onto our screen.  It is FINALLY time to start fixing that.  In this article I will create a new task called the advertising database task which will hold the history of advertising packets that I have seen.  I will update the Bluetooth Manager task to submit the advertising packets to a queue running in the advertising database task.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Create an Advertising Data Database Task

We need to create the file advertisingDatabase.h which will hold the task prototype (so that main can get going).

#pragma once

void adb_task(void *arg);

Then create the advertisingDatabase.c to hold the actual database code.  It will start with the definition of messages which can be sent to the task.  For now just “ADB_ADD”.  To make things a little bit simpler these command can have two data elements (which I call data0 and data1).  Then the main part of the task just

  1. Creates the queue to manage the messages
  2. Process the message until the end of time
#include "FreeRTOS.h"
#include "queue.h"

static QueueHandle_t adb_cmdQueue;
typedef enum {
    ADB_ADD,
} adb_cmd_t;

typedef struct
{
    adb_cmd_t cmd;
    void *data0;
    void *data1;
} adb_cmdMsg_t;

void adb_task(void *arg)
{
    // setup the queue
    adb_cmdMsg_t msg;

    adb_cmdQueue = xQueueCreate(10,sizeof(adb_cmdMsg_t));
    
    while(1)
    {
        BaseType_t status = xQueueReceive(adb_cmdQueue,&msg,portMAX_DELAY);
        if(status == pdTRUE) 
        {
            switch(msg.cmd)
            {
                case ADB_ADD:
                break;
            }

        }
    }
}

To start the task, you need to add it to main.c.

    xTaskCreate(adb_task,"adv database",configMINIMAL_STACK_SIZE*4,0,1,0);

When I build and program this, you can now see the new task.  Good that working.

AnyCloud> Unhandled Bluetooth Management Event: BTM_LOCAL_IDENTITY_KEYS_REQUEST_EVT
Started BT Stack Succesfully

AnyCloud> tasks
Name          State Priority   Stack  Num
------------------------------------------
usrcmd_ta       X       0       228     5
IDLE            R       0       115     7
Tmr Svc         B       0       223     8
CYBT_HCI_       B       5       950     3
sleep_tas       B       6       221     1
CYBT_BT_T       B       4       1371    2
blinkTask       B       0       98      4
adv datab       B       1       479     6
‘B’ – Blocked
‘R’ – Ready
‘D’ – Deleted (waiting clean up)
‘S’ – Suspended, or Blocked without a timeout
Stack = bytes free at highwater
AnyCloud>

Update the Advertising Database to Accept Submitted ADV Packets

If you recall our original setup was to take advertising packets in the Bluetooth Manager thread and print out the data.  The first thing that we want to fix up is the ability of the advertising database task to accept advertising packets which are pushed to its command queue.   To prepare for this I create two local variables to hold the data.

void adb_task(void *arg)
{
    // setup the queue
    adb_cmdMsg_t msg;
    wiced_bt_ble_scan_results_t *scan_result;
    uint8_t *data;

Then I update the ADB_ADD command.  My first, and really simple fix, is to grab the printing code from the Bluetooth Manager task.  Obviously this won’t be an improvement from the original program as far as the users goes, but it will verify that the tasks are working properly together.

                case ADB_ADD:
                    // Print the MAC Address
                    scan_result = (wiced_bt_ble_scan_results_t *)msg.data0;
                    data = (uint8_t *)msg.data1;

                    printf("MAC: ");
                    for(int i=0;i<BD_ADDR_LEN;i++)
                    {
                        printf("%02X:",scan_result->remote_bd_addr[i]);
                    }
                    // Print the RAW Data of the ADV Packet
                    printf(" Data: ");
                    int i=0;
                    while(data[i])
                    {
                        for(int j=0;j<data[i];j++)
                        {
                            printf("%02X ",data[i+1+j]);
                        }
                        i = i + data[i]+1;
                    }
                    printf("\n");
    
                    free(msg.data0);
                    free(msg.data1);
                break;

Then I add a command to the advertisingDatabase.h which the Bluetooth Manager task can call to submit advertising packets

void adb_addAdv(wiced_bt_ble_scan_results_t *scan_result,void *data);

The actual command in advertisingDatabase.c just takes the advertising information, puts it in a command message, then submits it to the command queue.

void adb_addAdv(wiced_bt_ble_scan_results_t *scan_result,void *data)
{
    adb_cmdMsg_t msg;
    msg.cmd = ADB_ADD;
    msg.data0 = (void *)scan_result;
    msg.data1 = (void *)data;
    xQueueSend(adb_cmdQueue,&msg,0); // If you loose an adv packet it is OK...
}

Update the Bluetooth Manager to Submit Adv Packets

Now I go and edit the bluetoothManager. c to submit packets rather than print them.  To do this I greatly simplify the callback.  There is one VERY important issue to deal with, which is one of those potential religious war issues.  Memory.

When you get the callback from the stack, it gives you POINTERS to data for the advertising packet that reside inside of buffers inside of the stack.  As soon as this callback returns this memory is purged.  To prevent this data from getting cleaned up by the stack I

  1. Malloc some memory for the wiced_bt_ble_scan_results
  2. Malloc some memory for the advertising data
  3. Make a copy of the data
  4. Submit it to the Advertising Database

I KNOW from the spec that the largest data packet is 31-bytes (actually it is 31-bytes + one more field with length 0).  So I know the maximum length is 32-bytes  This means that in many situations I will be copying GARBAGE into my buffer if the packet is less than 32 bytes long.  I think that this is simpler than calculating the length and then only copying that much data.

void btm_advCallback(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{
    wiced_bt_ble_scan_results_t *scan_result = malloc(sizeof(wiced_bt_ble_scan_results_t));
    uint8_t *data = malloc(32);
   
    memcpy(data,p_adv_data,32);
    memcpy(scan_result,p_scan_result->remote_bd_addr,BD_ADDR_LEN);
    adb_addAdv(scan_result,data);
}

When I run this updated program I should get the same stream of data coming out on the serial port.  Sure enough the new thread is working.

Create an Advertising Data Database

Now, lets create an actual database.  To simplify things my database is just an array of structures.  One structure per bluetooth device.  The structure will contain a pointer to the information about the device it just saw and the actual raw data.

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    uint8_t *data;
} adb_adv_t ;

#define ADB_MAX_SIZE (40)
adb_adv_t adb_database[ADB_MAX_SIZE];
int adb_db_count=0;

Then I will create several helper functions to work with the database

  1. Find devices in the database given a mac address
  2. Print an entry in the database
  3. Add entries to the database

First, find an entry in the database.  This function will search through the database and compare the mac address against the mac address in the database.  When the memcmp ==0 meaning it found a match, it will return that entry.

static int adb_db_find(wiced_bt_device_address_t *add)
{
    int rval=-1;
    for(int i=0;i<adb_db_count;i++)
    {
        if(memcmp(add,&adb_database[i].result->remote_bd_addr,BD_ADDR_LEN)==0)
        {
            rval = i;
            break;
        }
    }
    return rval;
}

The print function will make sure that you asked for a legal entry (much must be greater than 0… and less than the max).  Then it will print out the mac address and the raw data.  In a future post I will add a smarter print out.

static void adb_db_printEntry(int entry)
{
    if(!(entry>= 0 && entry <= adb_db_count))
    {
        printf("Illegal entry\n");
        return;
    }
    printf("%02d MAC: ",entry);

    for(int i=0;i<BD_ADDR_LEN;i++)
    {
        printf("%02X:",adb_database[entry].result->remote_bd_addr[i]);
    }

    // Print the RAW Data of the ADV Packet
    printf(" Data: ");
    int i=0;
    while(adb_database[entry].data[i])
    {
        for(int j=0;j<adb_database[entry].data[i];j++)
        {
            printf("%02X ",adb_database[entry].data[i+1+j]);
        }
        i = i + adb_database[entry].data[i]+1;
    }
    printf("\n");
}

To add an entry to the database, first make sure that it isn’t already in the database.  Then when you are sure that it isn’t the database, you just add the pointers to your table.  You need to make sure and not go beyond the end of the table, and if you did, you will have effectively blown away the last entry in the table.  Oh well.

static void adb_db_add(wiced_bt_ble_scan_results_t *scan_result,uint8_t *data)
{
 
    int entry = adb_db_find(&scan_result->remote_bd_addr);
    if(entry == -1)
    {
        
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].data = data;
        adb_db_printEntry(adb_db_count);
        adb_db_count = adb_db_count + 1;
        if(adb_db_count == ADB_MAX_SIZE)
        {
            printf("ADV Table Max Size\n");
            adb_db_count = adb_db_count - 1;
        }
    }
    else
    {
        free(scan_result);
        free(data);
    }
}

Add a Command to Print the Database

Now we want to add the ability to print from the command line.  So add a new command message to the list of legal commands.

typedef enum {
    ADB_ADD,
    ADB_PRINT,
} adb_cmd_t;

Then create a new function to print.  If you send in a “-1” it will print the whole table.  Otherwise just print the individual entry.

static void adb_printTable(int entry)
{
    if(entry == -1)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_db_printEntry(i);
        }

    }
    else
    {
        adb_db_printEntry(entry);
    }
    

}

Now edit usercmd.c to have the new command line.  Notice that I use “sscanf” which obviously has some issues.  Too bad.

static int usrcmd_print(int argc, char **argv)
{

    if(argc == 1)
    {
        adb_print(-1); // Print whole table
    }

    if(argc == 2)
    {
        int val;
        sscanf(argv[1],"%d",&val);
        adb_print(val);
    }

    return 0;
}

When I program the project it immediately prints out a bunch of devices that are at my house.  Then you can see I run the “print” command which prints the table.  Finally P do a print 0 to just print the first entry.

In the next article I will add smarter diagnostics to the advertising packets.

AnyCloud Bluetooth Advertising Scanner (Part 4)

Summary

In this article I update the AnyCloud BLE advertising scanner to use the btutil library that was created in the previous post.  In addition, I add a command queue to the bluetoothManger and enable a new command to turn on and off scanning.

Story

If you have been following along until now, which I imagine that you have if you are reading this,  you will have gotten a vomit of device data blasting out onto your serial console.  This isn’t very helpful.  So now what?  I am going to divide this problem into two parts

  1. Creating a new user command to turn on and off scanning (this article)
  2. Creating a database to manage the data + a set of commands to dump it (next article)

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Add the IoT Expert “btutil” Library

Before we actually start all of the command queue stuff, lets move to the btutil library that I talked about in the previous post.  To do this, add the library using the library manager.

Then delete bt_platform_cfg_settings.h and bt_platform_cfg_settings.c from your project.  Finally Rebuild and make sure that everything still works.  That is it.

Multithreading

Id like to explain that there is now some danger.  That danger comes from the fact that we have multiple tasks which are all accessing data plus functions that are talking to each other ASYNCHRONOUSLY.  Specifically we have:

  1. The Bluetooth Stack task – running the Bluetooth stack and management callback
  2. The Bluetooth Stack APIs – e.g. wiced_bt_ble_observe
  3. The usrcmd task – which is interacting with the user on the serial port and talking to the other tasks
  4. A timer_svc task – which runs software timers
  5. The advertising data (which I will start saving in the next article)

When faced with this situation what I typically like to do is provide thread safe public functions for each of the tasks.  Then any other task can call these functions and know that things are not going to get corrupted by a race condition.

To make the design thread safe, I typically like to put an RTOS Queue between the tasks.  These queues are a safe place to send and receive data in a “thread safe” way.  There are two basic design patterns that can be used

  1. Define a message structure (that gets pushed into the queue) and make it global (via a dot-h).  Define a queue handle and make it global (via a dot-h).  Then let any task build messages and push them into the queue to be received in the task that owns the queue.
  2. Define the message structure and queue.  Then define functions which are global (via a dot-h) which know how to interact with the queue.

I typically think that the 2nd method is better, so that is what I am going to do here.

  1. In BluetoothManager.h I will provide a function called “btm_cmdScan”
  2. The usrcmd task will call the btm_cmdScan function which will
  3. Create a btm_cmdMsg_t with the “scan” command and data of true/false
  4. Then push it into the Bluetooth Manager Command Queue
  5. Where a timer callback in the Bluetooth Manager Task will take it out of the queue
  6. Figure out that it is a “scan” command
  7. Then will either turn on or off scanning

Add a Queue to the Bluetooth Manager Thread

So we need two things a message to push into a queue (just a structure) and we need a queue to push it into.  First the message which is just a structure with two elements.  The first element is a command and the second element is some data of type void.  The meaning of the void *data will be different based on the command.

typedef struct {
	btm_cmd_t cmd;
	void *data;
} btm_cmdMsg_t;

But how about the command?  The command is just an enumerate list of commands which will now start with just one command.

typedef enum {
	BTM_SCAN,
} btm_cmd_t;

And know we need to define the queue.

#include "queue.h"
static QueueHandle_t btm_cmdQueue;

Before you can use the queue you need to initialize it.  The best place to initialize this queue is in the management callback right after the stack gets going.  You can see that I tell FreeRTOS that there is a queue which can hold up to 10 commands.  I also tell it that each command is the sizeof the command message.

    switch (event)
    {
        case BTM_ENABLED_EVT:
            printf("Started BT Stack Succesfully\n");
            btm_cmdQueue = xQueueCreate(10,sizeof(btm_cmdMsg_t));

Now we need to create a way for other tasks to create these command messages.  They will do this by calling a function which we will define in the bluetoothManager.h

void btm_cmdScan(bool enable);

This function will live in bluetoothManager.c and it simply

  1. Creates a command
  2. Set the actual command to scan
  3. Sets the void* data to be enable … in other words start or stop scanning.  Remember that a void * can be anything.  See I cast a bool to a void *
  4. Finally push the data into the command queue
void btm_cmdScan(bool enable)
{
    btm_cmdMsg_t msg;
    msg.cmd = BTM_SCAN;
    msg.data = (void *)enable;
  	xQueueSend(btm_cmdQueue, &msg,0);
}

Add a Timer to Process the Queue

So now we have a method to push items into the queue.  How do we get them out of the queue?  To do that I will use a Bluetooth Stack timer that will run every 50ms.

First, define the timer in bluetoothManager.c

#include "wiced_timer.h"
static wiced_timer_ext_t btm_mgmtQueueTimer;

Then define a function which the timer will call.  This function will

  1. Try to get a message out of the queue
  2. IF there is a message it will use a big switch to look at the possible messages
  3. If the message is a scan
  4. Then call the wiced function to either start “observing” or stop “observing”
static void btm_processBluetoothAppQueue()
{
	btm_cmdMsg_t msg;

	 BaseType_t rval;

	 rval = xQueueReceive( btm_cmdQueue,&msg,0);
	 if(rval == pdTRUE)
	 {
		 switch(msg.cmd)
		 {
		 case BTM_SCAN:
            wiced_bt_ble_observe((wiced_bool_t)msg.data,0,btm_advCallback);
			 break;
		 }
	 }
}

The last thing you need to do is start the timer.  The best place to start the timer is in the management callback where you need to

  1. Create the timer
  2. Tell it to start and run every 50ms
    switch (event)
    {
        case BTM_ENABLED_EVT:
            printf("Started BT Stack Succesfully\n");
            btm_cmdQueue = xQueueCreate(10,sizeof(btm_cmdMsg_t));
            wiced_init_timer_ext (&btm_mgmtQueueTimer, btm_processBluetoothAppQueue,0, WICED_TRUE);
            wiced_start_timer_ext (&btm_mgmtQueueTimer, 50);
        break;

A Potential Threading Bug

When I did the implementation originally I created what I thought was a threading bug.  Specifically I used the FreeRTOS timer to process the queue.  In other words instead of using a wiced_timer_ext_t I used a TimerHandle_t.  So what?

The wiced_timer_ext_t is run INSIDE of the BluetoothStack task where the TimerHandle_t is run inside of the Timer_SVC task.

So what?  I was afraid that the call to wiced_bt_ble_obsere was NOT thread safe and needed to be called inside of the same task as the stack.

After some digging I found out that the Bluetooth Stack is threadsafe, so I worried for no reason.  Well, actually, you can never worry enough about making these kinds of threading bugs because they are viscously difficult to debug.

Add a Scan Off & On Command

The last thing that you need to do is add an actual command to the usercmd task to call the bluetooth manager function to turn on and off scanning.

First, add a new prototype for your new command in usercmd.c.  Then add it to the list of legal commands.

static int usrcmd_scan(int argc, char **argv);


static const cmd_table_t cmdlist[] = {

.... deleted stuff

    { "scan","scan [on|off]", usrcmd_scan},

};

Then create the function to process the command line input and call the btm_scan function.

static int usrcmd_scan(int argc, char **argv)
{

    if(argc != 2)
        return 0;

    if(strcmp(argv[1],"on") == 0)
    {
        btm_cmdScan(true);
    }
    else if(strcmp(argv[1],"off") == 0)
    {
        btm_cmdScan(false);

    }
    return 0;

}

Now build it and run it.  You should still get adv packets barfing all over your screen.  But now you can turn on and off the scanning with “scan on” and “scan off”.  In the next article we will create a database to hold the scan packets.

AnyCloud Bluetooth Utilities Library

Summary

This article is a discussion of a library of utilities functions that support AnyCloud Bluetooth development.  It includes settings to configure the hardware, functions to decode stack events, functions to decode advertising packets etc.

Story

The Cypress, now Infineon, Modus Toolbox team has been working to bring the WICED Bluetooth devices to be closer and more compatible with the PSoC 6 tools.  One of the important enablement things we have done is turn on the WICED Bluetooth Host stack on the PSoC 6 so that it can effectively talk to the CYW43XXX Bluetooth / WiFi combo chips.

As I have been using all of the new stuff I found myself adding my own custom functionality…. and after a while I realized (finally) that I should put all of those little helper things into a library, specifically an IoT Expert library.  For now this library contains:

  • The Bluetooth Platform Configuration Settings
  • Functions to decode stack events
  • Functions to decode advertising packets

but imagine with time I will add more functions and templates.

bt_platform_cfg_settings

As I discussed in the article AnyCloud Bluetooth Advertising Scanner (Part 1), every single AnyCloud BLE Stack project will require a structure of type wiced_bt_cfg_settings_t.  This structure is used to setup the hardware before getting the stack going.  Remember you need to make a call like this to initialize the Bluetooth hardware.

    cybt_platform_config_init(&bt_platform_cfg_settings);

At some point this file will be included automatically for you by the Modus Toolbox team, but for now I have added this to my library.  This file look like this.  Basically a bunch of pin definitions which are set for you automatically by the BSP.  Obviously you can make your own, but this should work on all of the Cypress development kits (I think)

#include "cybt_platform_config.h"
#include "cybsp.h"
#include "wiced_bt_stack.h"

const cybt_platform_config_t bt_platform_cfg_settings =
{
    .hci_config =
    {
        .hci_transport = CYBT_HCI_UART,

        .hci =
        {
            .hci_uart =
            {
                .uart_tx_pin = CYBSP_BT_UART_TX,
                .uart_rx_pin = CYBSP_BT_UART_RX,
                .uart_rts_pin = CYBSP_BT_UART_RTS,
                .uart_cts_pin = CYBSP_BT_UART_CTS,

                .baud_rate_for_fw_download = 115200,
                .baud_rate_for_feature     = 115200,

                .data_bits = 8,
                .stop_bits = 1,
                .parity = CYHAL_UART_PARITY_NONE,
                .flow_control = WICED_TRUE
            }
        }
    },

    .controller_config =
    {
        .bt_power_pin      = CYBSP_BT_POWER,
        .sleep_mode =
        {
            #if (bt_0_power_0_ENABLED == 1) /* BT Power control is enabled in the LPA */
            #if (CYCFG_BT_LP_ENABLED == 1) /* Low power is enabled in the LPA, use the LPA configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYCFG_BT_DEV_WAKE_GPIO,
            .host_wakeup_pin = CYCFG_BT_HOST_WAKE_GPIO,
            .device_wake_polarity = CYCFG_BT_DEV_WAKE_POLARITY,
            .host_wake_polarity = CYCFG_BT_HOST_WAKE_IRQ_EVENT
            #else /* Low power is disabled in the LPA, disable low power */
            .sleep_mode_enabled = false
            #endif
            #else /* BT Power control is disabled in the LPA – default to BSP low power configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYBSP_BT_DEVICE_WAKE,
            .host_wakeup_pin = CYBSP_BT_HOST_WAKE,
            .device_wake_polarity = CYBT_WAKE_ACTIVE_LOW,
            .host_wake_polarity = CYBT_WAKE_ACTIVE_LOW
            #endif
        }
    },

    .task_mem_pool_size    = 2048
};

btutil_stack

When you startup the Bluetooth Host stack you are responsible for providing a “management callback” which has the function prototype

typedef wiced_result_t (wiced_bt_management_cback_t) (wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data);

The first parameter is an “event” which is  just an enumerated list of possible events by the Bluetooth Host Stack.  Here is the actual list.

enum wiced_bt_management_evt_e {
/* Bluetooth status events */
BTM_ENABLED_EVT,                                /**< Bluetooth controller and host stack enabled. Event data: wiced_bt_dev_enabled_t */
BTM_DISABLED_EVT,                               /**< Bluetooth controller and host stack disabled. Event data: NULL */
BTM_POWER_MANAGEMENT_STATUS_EVT,                /**< Power management status change. Event data: wiced_bt_power_mgmt_notification_t */
BTM_RE_START_EVT,                               /**< Bluetooth controller and host stack re-enabled. Event data: tBTM_ENABLED_EVT */
/* Security events */
BTM_PIN_REQUEST_EVT,                            /**< PIN request (used only with legacy devices). Event data: #wiced_bt_dev_name_and_class_t */
BTM_USER_CONFIRMATION_REQUEST_EVT,              /**< received USER_CONFIRMATION_REQUEST event (respond using #wiced_bt_dev_confirm_req_reply). Event data: #wiced_bt_dev_user_cfm_req_t */
BTM_PASSKEY_NOTIFICATION_EVT,                   /**< received USER_PASSKEY_NOTIFY event. Event data: #wiced_bt_dev_user_key_notif_t */
BTM_PASSKEY_REQUEST_EVT,                        /**< received USER_PASSKEY_REQUEST event @cond DUAL_MODE (respond using #wiced_bt_dev_pass_key_req_reply). Event data: #wiced_bt_dev_user_key_req_t @endcond
@note  BR/EDR Only */
BTM_KEYPRESS_NOTIFICATION_EVT,                  /**< received KEYPRESS_NOTIFY event. Event data: #wiced_bt_dev_user_keypress_t */
BTM_PAIRING_IO_CAPABILITIES_BR_EDR_REQUEST_EVT, /**< Requesting IO capabilities for BR/EDR pairing. Event data: #wiced_bt_dev_bredr_io_caps_req_t 
@note  BR/EDR Only */
BTM_PAIRING_IO_CAPABILITIES_BR_EDR_RESPONSE_EVT,/**< Received IO capabilities response for BR/EDR pairing. Event data: @cond DUAL_MODE #wiced_bt_dev_bredr_io_caps_rsp_t @endcond
@note  BR/EDR Only*/
BTM_PAIRING_IO_CAPABILITIES_BLE_REQUEST_EVT,    /**< Requesting IO capabilities for BLE pairing. Slave can check peer io capabilities in event data before updating with local io capabilities. Event data: #wiced_bt_dev_ble_io_caps_req_t */
BTM_PAIRING_COMPLETE_EVT,                       /**< received SIMPLE_PAIRING_COMPLETE event. Event data: #wiced_bt_dev_pairing_cplt_t */
BTM_ENCRYPTION_STATUS_EVT,                      /**< Encryption status change. Event data: #wiced_bt_dev_encryption_status_t */
BTM_SECURITY_REQUEST_EVT,                       /**< Security request (respond using #wiced_bt_ble_security_grant). Event data: #wiced_bt_dev_security_request_t */
BTM_SECURITY_FAILED_EVT,                        /**< Security procedure/authentication failed. Event data: #wiced_bt_dev_security_failed_t */
BTM_SECURITY_ABORTED_EVT,                       /**< Security procedure aborted locally, or unexpected link drop. Event data: #wiced_bt_dev_name_and_class_t */
BTM_READ_LOCAL_OOB_DATA_COMPLETE_EVT,           /**< Result of reading local OOB data @cond DUAL_MODE (#wiced_bt_dev_read_local_oob_data). Event data: #wiced_bt_dev_local_oob_t @endcond 
@note  BR/EDR Only */
BTM_REMOTE_OOB_DATA_REQUEST_EVT,                /**< OOB data from remote device @cond DUAL_MODE (respond using #wiced_bt_dev_remote_oob_data_reply). Event data: #wiced_bt_dev_remote_oob_t @endcond 
@note  BR/EDR Only */
BTM_PAIRED_DEVICE_LINK_KEYS_UPDATE_EVT,         /**< Updated remote device link keys (store device_link_keys to  NV memory). This is the place to
verify that the correct link key has been generated. Event data: #wiced_bt_device_link_keys_t */
BTM_PAIRED_DEVICE_LINK_KEYS_REQUEST_EVT,        /**< Request for stored remote device link keys (restore device_link_keys from NV memory). If successful, return WICED_BT_SUCCESS. Event data: #wiced_bt_device_link_keys_t */
BTM_LOCAL_IDENTITY_KEYS_UPDATE_EVT,             /**< Update local identity key (stored local_identity_keys NV memory). Event data: #wiced_bt_local_identity_keys_t */
BTM_LOCAL_IDENTITY_KEYS_REQUEST_EVT,            /**< Request local identity key (get local_identity_keys from NV memory). If successful, return WICED_BT_SUCCESS. Event data: #wiced_bt_local_identity_keys_t */
BTM_BLE_SCAN_STATE_CHANGED_EVT,                 /**< BLE scan state change. Event data: #wiced_bt_ble_scan_type_t */
BTM_BLE_ADVERT_STATE_CHANGED_EVT,               /**< BLE advertisement state change. Event data: #wiced_bt_ble_advert_mode_t */
/* BLE Secure Connection events */
BTM_SMP_REMOTE_OOB_DATA_REQUEST_EVT,            /**< SMP remote oob data request. Reply using wiced_bt_smp_oob_data_reply. Event data: #wiced_bt_smp_remote_oob_req_t  */
BTM_SMP_SC_REMOTE_OOB_DATA_REQUEST_EVT,         /**< LE secure connection remote oob data request. Reply using wiced_bt_smp_sc_oob_reply. Event data: #wiced_bt_smp_sc_remote_oob_req_t 
@note  BR/EDR Only */
BTM_SMP_SC_LOCAL_OOB_DATA_NOTIFICATION_EVT,     /**< LE secure connection local OOB data (wiced_bt_smp_create_local_sc_oob_data). Event data: #wiced_bt_smp_sc_local_oob_t */
BTM_SCO_CONNECTED_EVT,                          /**< SCO connected event. Event data: @cond DUAL_MODE #wiced_bt_sco_connected_t @endcond
@note  BR/EDR Only */
BTM_SCO_DISCONNECTED_EVT,                       /**< SCO disconnected event. Event data: @cond #wiced_bt_sco_disconnected_t @endcond
@note  BR/EDR Only */
BTM_SCO_CONNECTION_REQUEST_EVT,                 /**< SCO connection request event. Event data: @cond #wiced_bt_sco_connection_request_t @endcond
@note  BR/EDR Only */
BTM_SCO_CONNECTION_CHANGE_EVT,                  /**< SCO connection change event. Event data: @cond #wiced_bt_sco_connection_change_t @endcond
@note  BR/EDR Only */
BTM_BLE_CONNECTION_PARAM_UPDATE,                /**< BLE connection parameter update. Event data: #wiced_bt_ble_connection_param_update_t */
BTM_BLE_PHY_UPDATE_EVT,                         /**< BLE Physical link update. Event data: wiced_bt_ble_phy_update_t */
BTM_LPM_STATE_LOW_POWER,                        /**< BT device wake has been deasserted. Used for Host Stack Use Case. */
BTM_MULTI_ADVERT_RESP_EVENT,                    /**< Multi adv command status event Used for the status of the command sent */
#if SMP_CATB_CONFORMANCE_TESTER == TRUE
BTM_SMP_SC_PEER_INFO_EVT                        /** The Secure Connections support information of the peer device */
#endif
};

While you are trying to figure out what is going on during the development, it is very useful to be able to print out the name of the events (instead of the numbers).  In other words instead of doing

printf("Event = %02X\n",event);

it is way better to do

printf("Event Name=%s\n",btutil_getBTEventName(event));

While I was looking at an example project I found this function which I thought was awesome.

const char *btutil_getBTEventName(wiced_bt_management_evt_t event)
{
switch ( (int)event )
{
CASE_RETURN_STR(BTM_ENABLED_EVT)
CASE_RETURN_STR(BTM_DISABLED_EVT)
CASE_RETURN_STR(BTM_POWER_MANAGEMENT_STATUS_EVT)
CASE_RETURN_STR(BTM_PIN_REQUEST_EVT)
CASE_RETURN_STR(BTM_USER_CONFIRMATION_REQUEST_EVT)
CASE_RETURN_STR(BTM_PASSKEY_NOTIFICATION_EVT)
CASE_RETURN_STR(BTM_PASSKEY_REQUEST_EVT)
CASE_RETURN_STR(BTM_KEYPRESS_NOTIFICATION_EVT)
CASE_RETURN_STR(BTM_PAIRING_IO_CAPABILITIES_BR_EDR_REQUEST_EVT)
CASE_RETURN_STR(BTM_PAIRING_IO_CAPABILITIES_BR_EDR_RESPONSE_EVT)
CASE_RETURN_STR(BTM_PAIRING_IO_CAPABILITIES_BLE_REQUEST_EVT)
CASE_RETURN_STR(BTM_PAIRING_COMPLETE_EVT)
CASE_RETURN_STR(BTM_ENCRYPTION_STATUS_EVT)
CASE_RETURN_STR(BTM_SECURITY_REQUEST_EVT)
CASE_RETURN_STR(BTM_SECURITY_FAILED_EVT)
CASE_RETURN_STR(BTM_SECURITY_ABORTED_EVT)
CASE_RETURN_STR(BTM_READ_LOCAL_OOB_DATA_COMPLETE_EVT)
CASE_RETURN_STR(BTM_REMOTE_OOB_DATA_REQUEST_EVT)
CASE_RETURN_STR(BTM_PAIRED_DEVICE_LINK_KEYS_UPDATE_EVT)
CASE_RETURN_STR(BTM_PAIRED_DEVICE_LINK_KEYS_REQUEST_EVT)
CASE_RETURN_STR(BTM_LOCAL_IDENTITY_KEYS_UPDATE_EVT)
CASE_RETURN_STR(BTM_LOCAL_IDENTITY_KEYS_REQUEST_EVT)
CASE_RETURN_STR(BTM_BLE_SCAN_STATE_CHANGED_EVT)
CASE_RETURN_STR(BTM_BLE_ADVERT_STATE_CHANGED_EVT)
CASE_RETURN_STR(BTM_SMP_REMOTE_OOB_DATA_REQUEST_EVT)
CASE_RETURN_STR(BTM_SMP_SC_REMOTE_OOB_DATA_REQUEST_EVT)
CASE_RETURN_STR(BTM_SMP_SC_LOCAL_OOB_DATA_NOTIFICATION_EVT)
CASE_RETURN_STR(BTM_SCO_CONNECTED_EVT)
CASE_RETURN_STR(BTM_SCO_DISCONNECTED_EVT)
CASE_RETURN_STR(BTM_SCO_CONNECTION_REQUEST_EVT)
CASE_RETURN_STR(BTM_SCO_CONNECTION_CHANGE_EVT)
CASE_RETURN_STR(BTM_BLE_CONNECTION_PARAM_UPDATE)
#ifdef CYW20819A1
CASE_RETURN_STR(BTM_BLE_PHY_UPDATE_EVT)
#endif
}
return NULL;
}

And then I learned something new when I looked at the “function” CASE_RETURN_STR which used compiler trick to turn an enumerated value into a string.

#define CASE_RETURN_STR(enum_val)          case enum_val: return #enum_val;

This allows you to do this in your management callback (notice line 16 where the string is printed out)

wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data)
{
wiced_result_t result = WICED_BT_SUCCESS;
switch (event)
{
case BTM_ENABLED_EVT:
if (WICED_BT_SUCCESS == p_event_data->enabled.status)
{
printf("Started BT Stack Succesfully\n");
wiced_bt_ble_observe(WICED_TRUE,0,obv_callback);
}
break;
default:
printf("Unhandled Bluetooth Management Event: %s\n", btutil_getBTEventName(event));
break;
}
return result;
}

It turns out that there are several other places where the stack gives you an event-ish thing.  So these functions were created as well

#pragma once
const char *btutil_getBTEventName(wiced_bt_management_evt_t event);
const char *btutil_getBLEAdvertModeName(wiced_bt_ble_advert_mode_t mode);
const char *btutil_getBLEGattDisconnReasonName(wiced_bt_gatt_disconn_reason_t reason);
const char *btutil_getBLEGattStatusName(wiced_bt_gatt_status_t status);

btutil_adv_decode

In AnyCloud Bluetooth Advertising Scanner (Part 3) I discussed the format of the advertising packet.  So I created functions which will decode the data in the advertising packets.  More on the future article AnyCloud Bluetooth Advertising Scanner (Part 4 or maybe 5 or maybe 6).  Here are the functions:

#pragma once
wiced_bool_t btutil_isEddystone(uint8_t *data);
wiced_bool_t btutil_is_iBeacon(uint8_t *data);
wiced_bool_t btutil_isCypress(uint8_t *data);
int btutil_adv_len(uint8_t *packet);
void btutil_adv_printPacketDecode(uint8_t *packet);
void btutil_adv_printPacketBytes(uint8_t *packet);

btutil

And because I like to have just one include to get access to all of the function I created “btutil.h” which just includes all of the headers in one place.

#pragma once
#include "btutil_adv_decode.h"
#include "btutil_stack.h"
#include "btutil_general.h"
#include "bt_platform_cfg_settings.h"

Add to the IoT Expert Manifest

In order to set it up for my library to live in the library manager I updated the IoT Expert Manifest file to have a link to the GitHub repository

<middleware>
<name>Bluetooth Utilities</name>
<id>btutil</id>
<uri>https://github.com/iotexpert/btutil</uri>
<desc>A library of Bluetooth Debugging Utilties for Cypress PSoC6 Anycloud</desc>
<category>IoT Expert</category>
<req_capabilities>psoc6</req_capabilities>
<versions>
<version flow_version="1.0,2.0">
<num>master</num>
<commit>master</commit>
<desc>master</desc>
</version>
</versions>
</middleware>

And now the library manager has the IoT Expert Bluetooth Utilities.

 

 

AnyCloud Bluetooth Advertising Scanner (Part 3)

Summary

In this article I discuss BLE “Observing” and add that functionality to my PSoC 6 – CYW43xxx AnyCloud BLE Adverting Scanner project.

Story

In part1 of this series I discussed the pieces parts required to get the AnyCloud Bluetooth Stack operating using the AnyCloud SDK running on a PSoC 6 with a CY43xxx combo.  Then in part 2 I built a project with those parts and started up the Bluetooth Host stack.  The project didn’t really do anything, actually nothing, so it wasn’t very interesting, but it was going.  In this article I will discuss BLE advertising scanning, how to configure it in the AnyCloud project and finally how to add it to the project.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Explain BLE Advertising – Scanner/Observer

You might recall that there are four roles that a BLE device can perform

  • Peripheral – low power devices that broadcast advertisements, then accept a single connection
  • Central – devices like cell phones that connect to peripherals.  They may run multiple connections at a time.
  • Broadcaster – a nonconnectable peripheral that sends out advertisements
  • Observer – A central-like device that listens for broadcasters (or advertising peripherals)

And you might remember that advertisements are short, up to 31-byte, packets of data that give information which can be used for one or more of:

  • advertising the availability to connect
  • advertising services
  • advertising the name
  • advertising vendor specific information
  • advertising beacon data (like temperature or …)
  • advertising location

And, if you forgot, BLE operates on 40 channels.  But to save power in peripherals, all of the advertising happens on channel 37, 38 and 39.  Specifically a peripheral or broadcaster will send out the advertising data on channel 37, then 38 then 39, then wait… then do it again.  But why one channel at a time?  Because BLE radio’s can be tuned to transmit and receive on only one channel at a time (a power saving and complexity reducing feature)

Inside of the Central/Observer it will listen on channel 37 for a “window” amount of time.  Then it will do nothing for an interval-window amount of time.  Then it will do that same thing on channel 28 then 39.  But why only one channel at a time?  Same reason as above, it saves power and simplifies the design.  Why not have the window and the interval be the same?  Once again, it saves power.

Here is a picture:

But, what happens if you are not listening when the advertiser advertises?  You missed it.  Tough shit.  It turns out that setting the scan window and interval will greatly impact the probability that you hear advertisements.  And, you are more likely to hear advertisements because they are sent on three channels.  But it seems like it will never work.  Will it? … yes, of course, or they wouldn’t have done it that way 🙂

BLE Advertising – The Advertiser Peripheral or Broadcaster

So what exactly is inside of an advertising packet?  Volume 6 part B Section 2.3 of the bluetooth core spec describes the advertising protocol data unit (PDU)

But what is inside of the header?

This leaves us with what is inside of the “payload”.  The answer is that the ADV_IND Payload Data Unit (PDU) contains an address of 6-bytes plus up to 31 bytes of data.

The AdvA field shall contain the advertiser’s public or random device address as indicated by TxAdd.

The actual AdvData field is further broken up into “AD Structures” like this:

And what is the “AD Type”, well it is a one byte of one of the following:

And then where do you find the assigned numbers for the field types?  In the “Assigned Numbers and GAP“.  Here is a clip from the spec.

And conveniently enough we enumerated them for you inside of the SDK header file wiced_bt_ble.h

/** Advertisement data types */
enum wiced_bt_ble_advert_type_e {
BTM_BLE_ADVERT_TYPE_FLAG                        = 0x01,                 /**< Advertisement flags */
BTM_BLE_ADVERT_TYPE_16SRV_PARTIAL               = 0x02,                 /**< List of supported services - 16 bit UUIDs (partial) */
BTM_BLE_ADVERT_TYPE_16SRV_COMPLETE              = 0x03,                 /**< List of supported services - 16 bit UUIDs (complete) */
BTM_BLE_ADVERT_TYPE_32SRV_PARTIAL               = 0x04,                 /**< List of supported services - 32 bit UUIDs (partial) */
BTM_BLE_ADVERT_TYPE_32SRV_COMPLETE              = 0x05,                 /**< List of supported services - 32 bit UUIDs (complete) */
BTM_BLE_ADVERT_TYPE_128SRV_PARTIAL              = 0x06,                 /**< List of supported services - 128 bit UUIDs (partial) */
BTM_BLE_ADVERT_TYPE_128SRV_COMPLETE             = 0x07,                 /**< List of supported services - 128 bit UUIDs (complete) */
BTM_BLE_ADVERT_TYPE_NAME_SHORT                  = 0x08,                 /**< Short name */
BTM_BLE_ADVERT_TYPE_NAME_COMPLETE               = 0x09,                 /**< Complete name */
BTM_BLE_ADVERT_TYPE_TX_POWER                    = 0x0A,                 /**< TX Power level  */
BTM_BLE_ADVERT_TYPE_DEV_CLASS                   = 0x0D,                 /**< Device Class */
BTM_BLE_ADVERT_TYPE_SIMPLE_PAIRING_HASH_C       = 0x0E,                 /**< Simple Pairing Hash C */
BTM_BLE_ADVERT_TYPE_SIMPLE_PAIRING_RAND_C       = 0x0F,                 /**< Simple Pairing Randomizer R */
BTM_BLE_ADVERT_TYPE_SM_TK                       = 0x10,                 /**< Security manager TK value */
BTM_BLE_ADVERT_TYPE_SM_OOB_FLAG                 = 0x11,                 /**< Security manager Out-of-Band data */
BTM_BLE_ADVERT_TYPE_INTERVAL_RANGE              = 0x12,                 /**< Slave connection interval range */
BTM_BLE_ADVERT_TYPE_SOLICITATION_SRV_UUID       = 0x14,                 /**< List of solicitated services - 16 bit UUIDs */
BTM_BLE_ADVERT_TYPE_128SOLICITATION_SRV_UUID    = 0x15,                 /**< List of solicitated services - 128 bit UUIDs */
BTM_BLE_ADVERT_TYPE_SERVICE_DATA                = 0x16,                 /**< Service data - 16 bit UUID */
BTM_BLE_ADVERT_TYPE_PUBLIC_TARGET               = 0x17,                 /**< Public target address */
BTM_BLE_ADVERT_TYPE_RANDOM_TARGET               = 0x18,                 /**< Random target address */
BTM_BLE_ADVERT_TYPE_APPEARANCE                  = 0x19,                 /**< Appearance */
BTM_BLE_ADVERT_TYPE_ADVERT_INTERVAL             = 0x1a,                 /**< Advertising interval */
BTM_BLE_ADVERT_TYPE_LE_BD_ADDR                  = 0x1b,                 /**< LE device bluetooth address */
BTM_BLE_ADVERT_TYPE_LE_ROLE                     = 0x1c,                 /**< LE role */
BTM_BLE_ADVERT_TYPE_256SIMPLE_PAIRING_HASH      = 0x1d,                 /**< Simple Pairing Hash C-256 */
BTM_BLE_ADVERT_TYPE_256SIMPLE_PAIRING_RAND      = 0x1e,                 /**< Simple Pairing Randomizer R-256 */
BTM_BLE_ADVERT_TYPE_32SOLICITATION_SRV_UUID     = 0x1f,                 /**< List of solicitated services - 32 bit UUIDs */
BTM_BLE_ADVERT_TYPE_32SERVICE_DATA              = 0x20,                 /**< Service data - 32 bit UUID */
BTM_BLE_ADVERT_TYPE_128SERVICE_DATA             = 0x21,                 /**< Service data - 128 bit UUID */
BTM_BLE_ADVERT_TYPE_CONN_CONFIRM_VAL            = 0x22,                 /**< LE Secure Connections Confirmation Value */
BTM_BLE_ADVERT_TYPE_CONN_RAND_VAL               = 0x23,                 /**< LE Secure Connections Random Value */
BTM_BLE_ADVERT_TYPE_URI                         = 0x24,                 /**< URI */
BTM_BLE_ADVERT_TYPE_INDOOR_POS                  = 0x25,                 /**< Indoor Positioning */
BTM_BLE_ADVERT_TYPE_TRANS_DISCOVER_DATA         = 0x26,                 /**< Transport Discovery Data */
BTM_BLE_ADVERT_TYPE_SUPPORTED_FEATURES          = 0x27,                 /**< LE Supported Features */
BTM_BLE_ADVERT_TYPE_UPDATE_CH_MAP_IND           = 0x28,                 /**< Channel Map Update Indication */
BTM_BLE_ADVERT_TYPE_PB_ADV                      = 0x29,                 /**< PB-ADV */
BTM_BLE_ADVERT_TYPE_MESH_MSG                    = 0x2A,                 /**< Mesh Message */
BTM_BLE_ADVERT_TYPE_MESH_BEACON                 = 0x2B,                 /**< Mesh Beacon */
BTM_BLE_ADVERT_TYPE_PSRI                        = 0x2E,                 /**< Generic Audio Provate Set Random Identifier */
BTM_BLE_ADVERT_TYPE_3D_INFO_DATA                = 0x3D,                 /**< 3D Information Data */
BTM_BLE_ADVERT_TYPE_MANUFACTURER                = 0xFF                  /**< Manufacturer data */
};

How does scanning work in the AnyCloud Bluetooth Stack?

To turn on observing/scanning you need to call the function:

wiced_bt_dev_status_t wiced_bt_ble_observe (wiced_bool_t start, uint8_t duration, wiced_bt_ble_scan_result_cback_t *p_scan_result_cback);

Which will cause the host stack to tell the controller to start scanning for advertising packets.  It will set the scan window and scan interval the low duty scan settings from the bluetooth configuration structure… which we setup with the Bluetooth configurator.

 .low_duty_scan_interval          = CY_BT_LOW_DUTY_SCAN_INTERVAL,                              /**< Low duty scan interval */
.low_duty_scan_window            = CY_BT_LOW_DUTY_SCAN_WINDOW,                                /**< Low duty scan window */
.low_duty_scan_duration          = CY_BT_LOW_DUTY_SCAN_DURATION,                              /**< Low duty scan duration in seconds (0 for infinite) */

When the controller hears an advertising packet, it will send the HCI advertising report to the Bluetooth host stack, which with then call you back.  Specifically it will call you back by calling the p_scan_result_cback” function.

You provide the callback function which has the prototype:

typedef void (wiced_bt_ble_scan_result_cback_t) (wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data);

which contains two parameters, p_scan_result which is a structure that has the mac address and some thing data plus the p_adv_data which has the raw bytes of the advertising packet.

Add Observing to our Project

OK.  Lets add this to our project by creating a callback function like this:

Lines 5-9: Just prints out the raw bytes of the MAC address of the remote device, the one advertising

To print out the raw advertising data you need to remember that it is formatted as

  1. A length (of all of the data of the field)
  2. A type
  3. The rest of the data

When you find a field of length of 0 you know that you have reached the end of the data

On Lines 13-20: I print out one field at a time and the raw data

//
void obv_callback(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{
// Print the MAC Address
printf("MAC: ");
for(int i=0;i<6;i++)
{
printf("%02X:",p_scan_result->remote_bd_addr[i]);
}
// Print the RAW Data of the ADV Packet
printf(" Data: ");
int i=0;
while(p_adv_data[i])
{
for(int j=0;j<p_adv_data[i];j++)
{
printf("%02X ",p_adv_data[i+1+j]);
}
i = i + p_adv_data[i]+1;
}
printf("\n");
}

Then update the management callback to start the scanner after the stack is successfully started

    switch (event)
{
case BTM_ENABLED_EVT:
if (WICED_BT_SUCCESS == p_event_data->enabled.status)
{
printf("Started BT Stack Succesfully\n");
wiced_bt_ble_observe(WICED_TRUE,0,obv_callback);
}

Program and Test

Now when I run the program data comes blasting out of the screen because there are a boatload of ble devices in my house

AnyCloud> Unhandled Bluetooth Management Event: 0x16
Started BT Stack Succesfully
MAC: 76:99:58:E8:8B:1F:Data: 01 1A 0A 0C FF 4C 00 10 06 13 1A 54 F7 5A 7A 
MAC: 9E:7B:EF:0B:74:20:Data: 01 06 16 F7 FD 01 0C C2 81 CE 0C 74 58 77 19 C8 E3 84 A3 42 50 98 00 00 00 00 03 
MAC: 6F:11:7C:FF:02:13:Data: 01 1A 0A 05 FF 4C 00 10 06 03 1E BA 24 58 3D 
MAC: 3F:64:BE:4E:29:0C:Data: 01 04 FF 00 4C 02 15 26 86 F3 9C BA DA 46 58 85 4A A6 2E 7E 5E 8B 8D 00 01 00 00 C9 
MAC: 47:4B:F1:53:2C:84:Data: 01 06 FF 4C 00 10 05 08 18 79 1E C2 
MAC: C8:69:CD:18:BC:E6:Data: 01 1A 0A 0C FF 4C 00 10 05 0C 14 17 BF E9 
MAC: 27:F6:6F:1E:7A:78:Data: 01 1A FF 4C 00 09 06 03 12 C0 A8 20 0D 
MAC: 6F:AE:84:F6:6A:9F:Data: 01 06 FF 4C 00 10 05 08 18 79 1E C2 
MAC: 3F:64:BE:4E:29:0C:Data: 01 04 FF 00 4C 02 15 26 86 F3 9C BA DA 46 58 85 4A A6 2E 7E 5E 8B 8D 00 01 00 00 C9 
MAC: 41:EE:B4:9C:5C:5F:Data: 01 1A 0A 07 FF 4C 00 10 06 33 1A 49 59 46 B4 
MAC: 9E:7B:EF:0B:74:20:Data: 01 06 16 F7 FD 01 0C C2 81 CE 0C 74 58 77 19 C8 E3 84 A3 42 50 98 00 00 00 00 03 
MAC: C8:EB:ED:C8:AC:1C:Data: 01 0A 03 66 66 19 D0 07 FF EE 03 1C AC C8 ED EB C8 
MAC: 76:99:58:E8:8B:1F:Data: 01 1A 0A 0C FF 4C 00 10 06 13 1A 54 F7 5A 7A

In the next article Ill add some more smarts to manage the data to be easier to look at.

For your information here is all of the file bluetoothManager.c

#include <stdio.h>
#include <stdlib.h>
#include "cybsp.h"
#include "FreeRTOS.h"
#include "bluetoothManager.h"
#include "wiced_bt_stack.h"
#include "wiced_bt_dev.h"
#include "wiced_bt_trace.h"
//
void obv_callback(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{
// Print the MAC Address
printf("MAC: ");
for(int i=0;i<6;i++)
{
printf("%02X:",p_scan_result->remote_bd_addr[i]);
}
// Print the RAW Data of the ADV Packet
printf(" Data: ");
int i=0;
while(p_adv_data[i])
{
for(int j=0;j<p_adv_data[i];j++)
{
printf("%02X ",p_adv_data[i+1+j]);
}
i = i + p_adv_data[i]+1;
}
printf("\n");
}
/**************************************************************************************************
* Function Name: app_bt_management_callback()
***************************************************************************************************
* Summary:
*   This is a Bluetooth stack event handler function to receive management events from
*   the BLE stack and process as per the application.
*
* Parameters:
*   wiced_bt_management_evt_t event             : BLE event code of one byte length
*   wiced_bt_management_evt_data_t *p_event_data: Pointer to BLE management event structures
*
* Return:
*  wiced_result_t: Error code from WICED_RESULT_LIST or BT_RESULT_LIST
*
*************************************************************************************************/
wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data)
{
wiced_result_t result = WICED_BT_SUCCESS;
switch (event)
{
case BTM_ENABLED_EVT:
if (WICED_BT_SUCCESS == p_event_data->enabled.status)
{
printf("Started BT Stack Succesfully\n");
wiced_bt_ble_observe(WICED_TRUE,0,obv_callback);
}
else
{
printf("Error enabling BTM_ENABLED_EVENT\n");
}
break;
default:
printf("Unhandled Bluetooth Management Event: 0x%x\n", event);
break;
}
return result;
}

 

AnyCloud Bluetooth Advertising Scanner (Part 2)

Summary

The second article in a series discussing the creation of a PSoC 6 + CYW43xxx Advertising Scanner using the AnyCloud SDK.  This article will use the learning from Part 1 to create a template project that starts the BLE stack.

Story

In the previous article I discussed the structure of the Cypress/Infineon Bluetooth Stack and its integration into AnyCloud.  A bunch of “theory”, well I say BS to that.  Let’s build something.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Recall from Part 1 that you need three things to startup the Bluetooth Stack

  • The Hardware Configuration Structure that matches : cybt_platform_config_t
  • The Bluetooth Stack Configuration Structure that matches : wiced_bt_cfg_settings_t
  • The Bluetooth Management Callback that matches : typedef wiced_result_t (wiced_bt_management_cback_t) (wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data);

Then you need to call

  • The hardware initialization function : cybt_platform_config_init
  • The stack initialization function : wiced_bt_stack_init

Ok let’s do this!

Basic Project

You can do all of these steps from the Eclipse IDE for ModusToolbox.  Or you can do it from the individual programs and the command line.  I like Visual Studio code, so this article will be done completely from the command line and individual configurators.

Run the new project creator from the start menu.  Start by creating a project for the development kit that you have, in my case the one currently plugged into my computer is the CY8CKIT-062S2-43012, so that is what I pick.  But, this project will work with any of the WiFI/BT combo chips attached to PSoC 6.

In previous articles I discussed the template that I use to get things going with FreeRTOS.  I won’t discuss that here, but I want FreeRTOS and the NTShell, so pick the IoT Expert FreeRTOS NTShell Template.

After about a minute you should have a project.  I always like to build the project to make sure that everything is working before I get too far down the road of modifying anything.  Run “make -j build”

arh (master) AnyCloudBLEScanner $ make -j build
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 205 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 503 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 193 file(s)
Compiling app file lowPower.c
Compiling app file main.c
Compiling app file usrcmd.c
Compiling ext file startup_psoc6_02_cm4.S
..... a bunch of lines deleted
Compiling ext file psoc6_01_cm0p_sleep.c
Compiling ext file psoc6_02_cm0p_sleep.c
Compiling ext file psoc6_03_cm0p_sleep.c
Compiling ext file psoc6_04_cm0p_sleep.c
Compiling ext file cy_retarget_io.c
Linking output file MTBShellTemplate.elf
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  54876      | 
| .ARM.exidx           |  0x1000f65c   |  8          | 
| .copy.table          |  0x1000f664   |  24         | 
| .zero.table          |  0x1000f67c   |  8          | 
| .data                |  0x080022e0   |  1688       | 
| .cy_sharedmem        |  0x08002978   |  8          | 
| .noinit              |  0x08002980   |  148        | 
| .bss                 |  0x08002a14   |  2136       | 
| .heap                |  0x08003270   |  1029520    | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           64812      
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033500    

Then to be sure it is working, program the development kit.

arh (master) AnyCloudBLEScanner $ make program
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 205 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 503 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 193 file(s)
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  54876      | 
| .ARM.exidx           |  0x1000f65c   |  8          | 
| .copy.table          |  0x1000f664   |  24         | 
| .zero.table          |  0x1000f67c   |  8          | 
| .data                |  0x080022e0   |  1688       | 
| .cy_sharedmem        |  0x08002978   |  8          | 
| .noinit              |  0x08002980   |  148        | 
| .bss                 |  0x08002a14   |  2136       | 
| .heap                |  0x08003270   |  1029520    | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           64812      
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033500    
Programming target device... 
Open On-Chip Debugger 0.10.0+dev-4.1.0.1058 (2020-08-11-03:45)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
adapter speed: 2000 kHz
adapter srst delay: 25
adapter srst pulse_width: 25
** Auto-acquire enabled, use "set ENABLE_ACQUIRE 0" to disable
cortex_m reset_config sysresetreq
cortex_m reset_config sysresetreq
Info : Using CMSIS loader 'CY8C6xxA_SMIF' for bank 'psoc6_smif0_cm0' (footprint 14672 bytes)
Warn : SFlash programming allowed for regions: USER, TOC, KEY
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : KitProg3: FW version: 1.14.514
Info : KitProg3: Pipelined transfers disabled, please update the firmware
Info : VTarget = 3.215 V
Info : kitprog3: acquiring the device...
Info : clock speed 2000 kHz
Info : SWD DPIDR 0x6ba02477
Info : psoc6.cpu.cm0: hardware has 4 breakpoints, 2 watchpoints
***************************************
** Silicon: 0xE453, Family: 0x102, Rev.: 0x12 (A1)
** Detected Device: CY8C624ABZI-S2D44
** Detected Main Flash size, kb: 2048
** Flash Boot version: 3.1.0.378
** Chip Protection: NORMAL
***************************************
Info : psoc6.cpu.cm4: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for psoc6.cpu.cm0 on 3333
Info : Listening on port 3333 for gdb connections
Info : starting gdb server for psoc6.cpu.cm4 on 3334
Info : Listening on port 3334 for gdb connections
Info : SWD DPIDR 0x6ba02477
Info : kitprog3: acquiring the device...
psoc6.cpu.cm0 halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x00000190 msp: 0x080ff800
** Device acquired successfully
** psoc6.cpu.cm4: Ran after reset and before halt...
psoc6.cpu.cm4 halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0000012a msp: 0x080ff800
** Programming Started **
auto erase enabled
Info : Flash write discontinued at 0x1000179c, next section at 0x10002000
Info : Padding image section 0 at 0x1000179c with 100 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
Info : Padding image section 1 at 0x1000fd24 with 220 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
wrote 62976 bytes from file /Users/arh/mtw/AnyCloudBLEScanner/build/CY8CKIT-062S2-43012/Debug/MTBShellTemplate.hex in 2.069358s (29.719 KiB/s)
** Programming Finished **
** Verify Started **
verified 62656 bytes in 0.122208s (500.683 KiB/s)
** Verified OK **
** Resetting Target **
Info : SWD DPIDR 0x6ba02477
shutdown command invoked
Info : psoc6.dap: powering down debug domain...
arh (master) AnyCloudBLEScanner $

When that is done, open up a terminal window and you should have a functioning base project.  Notice that I ran “help” and “tasks” from the command shell.

Now that we have a basic project working, add the Bluetooth libraries.  Run the library manager by typing “make modlibs”.  Then select “bluetooth-freertos” and the library manager will automatically select the other libraries you need.  Press Update then Close.

Next, run the bluetooth configurator by running “make config_bt”  This tool will help you make the bluetooth stack configuration structure.  When the configurator starts, press “New”

Then select our device (the PSoC 6 and the Combo chip)

Click on the “GAP Settings”.  Then press the Plus and add “Observer configuration”

Then setup the scan settings (more detail on these numbers in the next article)

  • Low duty scan window (ms) = 60
  • Low duty scan interval (ms) = 60
  • Low duty scan timeout = deselected (meaning no timeout)

Then save your configuration file.  Notice that I called it “btconfig”

When you are done you will have a directory called “GeneratedSource” inside of your project with the needed files.

The next step is to fix up the Makefile.  I like changing the name of the “App”.

# Name of application (used to derive name of final linked file).
APPNAME=AnyCloudBLEScanner

Then you need the “FREERTOS WICED_BLE” components.

# Enable optional code that is ordinarily disabled by default.
#
# Available components depend on the specific targeted hardware and firmware
# in use. In general, if you have
#
#    COMPONENTS=foo bar
#
# ... then code in directories named COMPONENT_foo and COMPONENT_bar will be
# added to the build
#
COMPONENTS=FREERTOS WICED_BLE

If you run make vscode it will update the workspace with all of the stuff needed for Visual Studio Code to be able to find all of the files.

arh (master) AnyCloudBLEScanner $ make vscode
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 230 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 22 .a file(s)
-> Found 561 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Generating IDE files =
==============================================================================
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
echo "The existing MTBShellTemplate.code-workspace file has been saved to .vscode/backup";
The existing MTBShellTemplate.code-workspace file has been saved to .vscode/backup
The existing c_cpp_properties.json file has been saved to .vscode/backup
The existing launch.json file has been saved to .vscode/backup
Modifying existing settings.json file. Check against the backup copy in .vscode/backup
The existing tasks.json file has been saved to .vscode/backup
Generated Visual Studio Code files: c_cpp_properties.json launch.json openocd.tcl settings.json tasks.json
J-Link users, please see the comments at the top of the launch.json
file about setting the location of the gdb-server.
Instructions:
1. Review the modustoolbox.toolsPath property in .vscode/settings.json
2. Open VSCode
3. Install "C/C++" and "Cortex-Debug" extensions
4. File->Open Folder (Welcome page->Start->Open folder)
5. Select the app root directory and open
6. Builds: Terminal->Run Task
7. Debugging: "Bug icon" on the left-hand pane
arh (master) AnyCloudBLEScanner $

Inside of Visual Studio Code, create a new file called “bt_platform_cfg_settings.h” and add:

#include "cybt_platform_config.h"
#include "cybsp.h"
#include "wiced_bt_stack.h"
extern const cybt_platform_config_t bt_platform_cfg_settings;

Inside of Visual Studio Code, create a new file called “bt_platform_cfg_settings.c” and add:

#include "cybt_platform_config.h"
#include "cybsp.h"
#include "wiced_bt_stack.h"

const cybt_platform_config_t bt_platform_cfg_settings =
{
    .hci_config =
    {
        .hci_transport = CYBT_HCI_UART,

        .hci =
        {
            .hci_uart =
            {
                .uart_tx_pin = CYBSP_BT_UART_TX,
                .uart_rx_pin = CYBSP_BT_UART_RX,
                .uart_rts_pin = CYBSP_BT_UART_RTS,
                .uart_cts_pin = CYBSP_BT_UART_CTS,

                .baud_rate_for_fw_download = 115200,
                .baud_rate_for_feature     = 115200,

                .data_bits = 8,
                .stop_bits = 1,
                .parity = CYHAL_UART_PARITY_NONE,
                .flow_control = WICED_TRUE
            }
        }
    },

    .controller_config =
    {
        .bt_power_pin      = CYBSP_BT_POWER,
        .sleep_mode =
        {
            #if (bt_0_power_0_ENABLED == 1) /* BT Power control is enabled in the LPA */
            #if (CYCFG_BT_LP_ENABLED == 1) /* Low power is enabled in the LPA, use the LPA configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYCFG_BT_DEV_WAKE_GPIO,
            .host_wakeup_pin = CYCFG_BT_HOST_WAKE_GPIO,
            .device_wake_polarity = CYCFG_BT_DEV_WAKE_POLARITY,
            .host_wake_polarity = CYCFG_BT_HOST_WAKE_IRQ_EVENT
            #else /* Low power is disabled in the LPA, disable low power */
            .sleep_mode_enabled = false
            #endif
            #else /* BT Power control is disabled in the LPA – default to BSP low power configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYBSP_BT_DEVICE_WAKE,
            .host_wakeup_pin = CYBSP_BT_HOST_WAKE,
            .device_wake_polarity = CYBT_WAKE_ACTIVE_LOW,
            .host_wake_polarity = CYBT_WAKE_ACTIVE_LOW
            #endif
        }
    },

    .task_mem_pool_size    = 2048
};

Inside of Visual Studio Code, create bluetoothManager.h.  Remember this is the Bluetooth Stack Management Callback

#pragma once
#include "wiced_bt_stack.h"
#include "wiced_bt_dev.h"
wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data);

Inside of Visual Studio code, create bluetoothManager.c.  This function does a whole lotta nothin… except saying that things got started.

#include <stdio.h>
#include <stdlib.h>
#include "cybsp.h"
#include "FreeRTOS.h"
#include "bluetoothManager.h"
#include "wiced_bt_stack.h"
#include "wiced_bt_dev.h"
#include "wiced_bt_trace.h"
/**************************************************************************************************
* Function Name: app_bt_management_callback()
***************************************************************************************************
* Summary:
*   This is a Bluetooth stack event handler function to receive management events from
*   the BLE stack and process as per the application.
*
* Parameters:
*   wiced_bt_management_evt_t event             : BLE event code of one byte length
*   wiced_bt_management_evt_data_t *p_event_data: Pointer to BLE management event structures
*
* Return:
*  wiced_result_t: Error code from WICED_RESULT_LIST or BT_RESULT_LIST
*
*************************************************************************************************/
wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data)
{
wiced_result_t result = WICED_BT_SUCCESS;
switch (event)
{
case BTM_ENABLED_EVT:
if (WICED_BT_SUCCESS == p_event_data->enabled.status)
{
printf("Started BT Stack Succesfully\n");
}
else
{
printf("Error enabling BTM_ENABLED_EVENT\n");
}
break;
default:
printf("Unhandled Bluetooth Management Event: 0x%x\n", event);
break;
}
return result;
}

Next, update main.c with the required includes.

#include "bluetoothManager.h"
#include "cycfg_bt_settings.h"
#include "bt_platform_cfg_settings.h"

Then update main.c to start the stack

    cybt_platform_config_init(&bt_platform_cfg_settings);
wiced_bt_stack_init (app_bt_management_callback, &wiced_bt_cfg_settings);

Now build and program it, remember “make -j build” and “make program”.  Look, we have a functioning stack with the two bluetooth thread running.

In the next article Ill finally get around to building the Bluetooth Scanner.

AnyCloud Bluetooth Advertising Scanner (Part 1)

Summary

The first of several articles discussing the use of the AnyCloud BLE Stack to build advertising scanner/observers.

Story

A few summers ago while I was writing the WICED Bluetooth Academy book, I created a WICED based BLE advertising scanner.  Actually, I created a framework for the scanner and the remarkable summer intern I had finished the work.  That project has been sitting in the source code repository for the Bluetooth class, mostly only shown to face-to-face students.  This scanner is built using some of the original code combined with the new AnyCloud Bluetooth SDK.  It will act sort-of-like LightBlue or one of the other Bluetooth advertising scanners you might run on your phone, but with a serial console.

Sometime in the last few months we released the Bluetooth SDK for AnyCloud (things have been crazy and I have lost track of time)  This SDK has all of the stuff needed to add Bluetooth to your AnyCloud project using one of the Cypress Bluetooth/WiFi combo chips.  I had not had a chance to try it out, so I decided to build a Bluetooth project and then port the scanning code.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Bluetooth Application Architecture

Bluetooth applications are divided into these four pieces

  1. You user application which responds to events and sends messages from/to the Bluetooth host stack
  2. A Bluetooth Host Stack
  3. A Bluetooth Controller Stack
  4. The Bluetooth Radio

These four pieces can be divided into multiple chips, as few as one or as many as four.  However, for this article, the project will be built to run on a PSoC 6 + CYW43012 WiFi/Bluetooth Combo chip.  Specifically:

  1. My scanner application running on the PSoC 6
  2. The Bluetooth Host Stack running on the PSoC 6
  3. The BlueTooth Controller Firmware running on the CYW43012
  4. A Bluetooth Radio on the CYW43012

But how do they talk?  Simple, there is:

  1. A UART Host Controller Interface (HCI) between the two chips
  2. A GPIO to serve as a deep sleep wakeup from the 43012 –> PSoC 6
  3. A GPIO to serve as the bluetooth controller wakeup from the PSoC 6 –> 43012
  4. A GPIO to turn on the Bluetooth regulator from the PSoC 6 –> 43012

Here is the block diagram from the CY8CKIT-062S2-43012 Kit Guide.  Notice that signals labeled UART and Control going between the PSoC 6 and the CYW43012.

And when you read more deeply into the schematic you can see the signals labeled

  • BT_UART_TXD/RXD/CTS/RTS
  • BT_HOST_WAKE
  • BT_DEV_WAKE
  • BT_REG_ON

How to Start the AnyCloud Bluetooth Stack

To actually start the AnyCloud Bluetooth stack you will call two functions

  1. cybt_platform_config_init – that will setup the hardware interface to the CYW43012
  2. wiced_bt_stack_init that will:
    1. Start a task to manage the Host Controller Interface
    2. Download the controller firmware to the CYW43012
    3. Start a task to manage the host stack
    4. Initialize both the host and the controller
    5. Call you back when that is all done

Here is an example from main.

    cybt_platform_config_init(&bt_platform_cfg_settings);
wiced_bt_stack_init (app_bt_management_callback, &wiced_bt_cfg_settings);

When you look at these two function calls, you will find that you need to provide three things:

  1. A platform hardware configuration structure called bt_platform_cfg_settings
  2. The Bluetooth stack configuration settings structure called wiced_bt_cfg_settings
  3. A management callback called app_bt_management_callback

bt_platform_cfg_settings

The purpose of the hardware configuration structure is to define the UART + parameters and the wakeup GPIOs.  Specifically, the hardware configuration structure defines the configuration of the host controller interface (hci)

  1. The HCI transport scheme (in this case UART)
  2. The pins of the UART
  3. Baud Rate
  4. Data Bits
  5. Stop Bits
  6. Parity
  7. Flow Control

And the controller low power behavior (in the .controller_config member)

This is a fairly standard configuration and I think that we should help you by providing this structure somewhere in the BSP.  But for now, you need to provide it (in an upcoming article I’ll update the IoT Expert Bluetooth Library to provide it).  Here is the specific structure that I will be using.

const cybt_platform_config_t bt_platform_cfg_settings =
{
.hci_config =
{
.hci_transport = CYBT_HCI_UART,
.hci =
{
.hci_uart =
{
.uart_tx_pin = CYBSP_BT_UART_TX,
.uart_rx_pin = CYBSP_BT_UART_RX,
.uart_rts_pin = CYBSP_BT_UART_RTS,
.uart_cts_pin = CYBSP_BT_UART_CTS,
.baud_rate_for_fw_download = 115200,
.baud_rate_for_feature     = 115200,
.data_bits = 8,
.stop_bits = 1,
.parity = CYHAL_UART_PARITY_NONE,
.flow_control = WICED_TRUE
}
}
},
.controller_config =
{
.bt_power_pin      = CYBSP_BT_POWER,
.sleep_mode =
{
#if (bt_0_power_0_ENABLED == 1) /* BT Power control is enabled in the LPA */
#if (CYCFG_BT_LP_ENABLED == 1) /* Low power is enabled in the LPA, use the LPA configuration */
.sleep_mode_enabled = true,
.device_wakeup_pin = CYCFG_BT_DEV_WAKE_GPIO,
.host_wakeup_pin = CYCFG_BT_HOST_WAKE_GPIO,
.device_wake_polarity = CYCFG_BT_DEV_WAKE_POLARITY,
.host_wake_polarity = CYCFG_BT_HOST_WAKE_IRQ_EVENT
#else /* Low power is disabled in the LPA, disable low power */
.sleep_mode_enabled = false
#endif
#else /* BT Power control is disabled in the LPA – default to BSP low power configuration */
.sleep_mode_enabled = true,
.device_wakeup_pin = CYBSP_BT_DEVICE_WAKE,
.host_wakeup_pin = CYBSP_BT_HOST_WAKE,
.device_wake_polarity = CYBT_WAKE_ACTIVE_LOW,
.host_wake_polarity = CYBT_WAKE_ACTIVE_LOW
#endif
}
},
.task_mem_pool_size    = 2048
};

wiced_bt_cfg_settings

The Cypress WICED Bluetooth Stack has a boatload of configuration settings.  When you call the stack start function you need to provide all of those settings in a structure of type “wiced_bt_cfg_settings_t” which is actually a structure of structures.  There are several basic ways to set these settings

  • Start from scratch and build you own settings
  • Copy from an example project
  • Use the Bluetooth Configurator to generate the structure

For the purposes of THIS project I started by copying the structure from on of the example projects and then modifying the three numbers that were relevant to me.  Specifically

  • max_simultanous_link – which I changed to 0 because this is simply a Bluetooth Observer
  • low_duty_scan_interval – how long in the window to listen for advertising packets
  • low_duty_scan_window – how wide the window of listening should be
const wiced_bt_cfg_settings_t wiced_bt_cfg_settings =
{
.device_name                         = (uint8_t *)BT_LOCAL_NAME,                                   /**< Local device name (NULL terminated) */
.device_class                        = {0x00, 0x00, 0x00},                                         /**< Local device class */
.security_requirement_mask           = BTM_SEC_NONE,                                               /**< Security requirements mask (BTM_SEC_NONE, or combinination of BTM_SEC_IN_AUTHENTICATE, BTM_SEC_OUT_AUTHENTICATE, BTM_SEC_ENCRYPT (see #wiced_bt_sec_level_e)) */
.max_simultaneous_links              = 0,                                                          /**< Maximum number simultaneous links to different devices */
.ble_scan_cfg =                                                 /* BLE scan settings  */
{
.scan_mode                       = BTM_BLE_SCAN_MODE_PASSIVE,                                  /**< BLE scan mode (BTM_BLE_SCAN_MODE_PASSIVE, BTM_BLE_SCAN_MODE_ACTIVE, or BTM_BLE_SCAN_MODE_NONE) */
/* Advertisement scan configuration */
.high_duty_scan_interval         = WICED_BT_CFG_DEFAULT_HIGH_DUTY_SCAN_INTERVAL,               /**< High duty scan interval */
.high_duty_scan_window           = WICED_BT_CFG_DEFAULT_HIGH_DUTY_SCAN_WINDOW,                 /**< High duty scan window */
.high_duty_scan_duration         = 0,                                                          /**< High duty scan duration in seconds (0 for infinite) */
.low_duty_scan_interval          = 96,                                                         /**< Low duty scan interval  */
.low_duty_scan_window            = 96,                                                         /**< Low duty scan window */
.low_duty_scan_duration          = 0,                                                          /**< Low duty scan duration in seconds (0 for infinite) */
/* Connection scan configuration */
.high_duty_conn_scan_interval    = WICED_BT_CFG_DEFAULT_HIGH_DUTY_CONN_SCAN_INTERVAL,          /**< High duty cycle connection scan interval */
.high_duty_conn_scan_window      = WICED_BT_CFG_DEFAULT_HIGH_DUTY_CONN_SCAN_WINDOW,            /**< High duty cycle connection scan window */
.high_duty_conn_duration         = 0,                                                         /**< High duty cycle connection duration in seconds (0 for infinite) */
.low_duty_conn_scan_interval     = WICED_BT_CFG_DEFAULT_LOW_DUTY_CONN_SCAN_INTERVAL,           /**< Low duty cycle connection scan interval */
.low_duty_conn_scan_window       = WICED_BT_CFG_DEFAULT_LOW_DUTY_CONN_SCAN_WINDOW,             /**< Low duty cycle connection scan window */
.low_duty_conn_duration          = 0,                                                         /**< Low duty cycle connection duration in seconds (0 for infinite) */
/* Connection configuration */
.conn_min_interval               = WICED_BT_CFG_DEFAULT_CONN_MIN_INTERVAL,                     /**< Minimum connection interval */
.conn_max_interval               = WICED_BT_CFG_DEFAULT_CONN_MAX_INTERVAL,                     /**< Maximum connection interval */
.conn_latency                    = WICED_BT_CFG_DEFAULT_CONN_LATENCY,                          /**< Connection latency */
.conn_supervision_timeout        = WICED_BT_CFG_DEFAULT_CONN_SUPERVISION_TIMEOUT,              /**< Connection link supervision timeout */
},
.default_ble_power_level            = 12                                                           /**< Default LE power level, Refer lm_TxPwrTable table for the power range */
};

app_bt_management_callback

The last thing that you need to provide is a management callback.  This function is called by the Bluetooth Stack when a “management event” occurs.  There is a big-long-list of enumerated events of type wiced_bt_management_event_t.  The events include things like the the stack started “BTM_ENABLED_EVENT”.  Each event may have data associated with the event which is passed to you in a pointer to wiced_bt_management_event_data_t.

You typically deal with these events with a giant switch statement like this:

wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data)
{
wiced_result_t result = WICED_BT_SUCCESS;
switch (event)
{
case BTM_ENABLED_EVT:
if (WICED_BT_SUCCESS == p_event_data->enabled.status)
{
printf("Stack Started Successfully\n");
}
break;
default:
printf("Unhandled Bluetooth Management Event: 0x%x %s\n", event, btutil_getBTEventName(event));
break;
}
return result;
}

Tasks

The Bluetooth stack on the PSoC6 is operated with two tasks.  Specifically, when you call the wiced_bt_stack_init it will startup:

  1. CYBT_HCI_Task – a task that sends and receives HCI packets going to the Radio chip
  2. CY_BT_Task – a task that manages the Bluetooth Host Stack

Here is print of the task list from my project:

AnyCloud> tasks
Name          State Priority   Stack  Num
------------------------------------------
nt shell        X       0       236     5
IDLE            R       0       115     6
blink           B       0       98      4
CYBT_BT_Task    B       4       1371    2
sleep_task      B       6       217     1
CYBT_HCI_Task   B       5       950     3
Tmr Svc         B       6       76      7
‘B’ – Blocked
‘R’ – Ready
‘D’ – Deleted (waiting clean up)
‘S’ – Suspended, or Blocked without a timeout
Stack = bytes free at highwater

 

Now with the background in place, in the next article I will discuss Bluetooth advertising and how to build the observer project.

Update Project to ModusToolbox 2.2

Summary

This article walks you through the steps that I took to convert a ModusToolbox 2.1 project using the “dot lib” flow into a Modus Toolbox 2.2 project using the “mtb” flow.

Story

I have been working on way way to many different things and not finishing nearly enough things.  Oh well.  One of the projects that I have been working on is an implementation of a Bluetooth Observer to process iBeacon packets from a Tilt Hydrometer (more on that in a future article).  This project was started in Modus Toolbox 2.1.  It will continue to work perfectly in ModusToolbox 2.2 as it is 100% backwards compatible, but, I want to use some of the new features of 2.2.

As I discussed in a previous article, there are now two library systems in Modus Toolbox.

  • The “mtb” flow – which allows for “shared libraries”
  • The “dot lib” flow – which has all libraries embedded into the project.

If you want to convert your Modus Toolbox 2.1 project Modus Toolbox 2.2 you can follow a process like this:

  1. Backup the project into Git
  2. Create a template Modus Toolbox 2.2 project (so I have a “clean” copy of the Makefile and “mtb” files)
  3. Fix the Makefile
  4. Examine and Update the library dependencies
  5. Test

Backup Everything

Before you torch your perfectly good project you should back it up.  I typically use Git.  Actually I always use Git.  As I wrote this article I realized that I should have tagged the version before I started the updates.  To figure out which commit that was I ran a “git log” and then I scrolled through my new commits until I found the last one before the conversion to 2.2.  Which requires me to admit that when I left the project a couple of weeks ago there were two files which I hadn’t checked.  So I blindly checked them in and then told the truth in my comment.

commit c256b822b2c956b17ec6cb35868e171d71dda0fe (HEAD, tag: mtb2.1)
Author: iotexpert <engineer@iotexpert.com>
Date:   Sat Oct 10 06:47:40 2020 -0400
updates which I have no idea what they are

Now that I have the right commit, I tagged with with a “mtb 2.1” tag.

arh (master) TiltHydrometer $ git tag -a "mtb2.1" c256b822b2c956b17ec6cb35868e171d71dda0fe

Now I can go back to my 2.1 project by running “git checkout mtb2.1”

Create a Template Project

In order to use the new flow you need to do two things.

  1. Fix the Makefile
  2. Change the “dot lib” files to “mtb” files

I never can remember the secret incantation to put in either place.  So, I create a blank project called “Simple22” to allow me to steal the bits that I want.

Makefile

In Modus Toolbox 2.2 we added two new variables to the Makefile which allow you to specify the location of the shared library.  First, here is the relevant section of the original 2.1 Makefile

################################################################################
# Paths
################################################################################
# Relative path to the project directory (default is the Makefile's directory).
#
# This controls where automatic source code discovery looks for code.
CY_APP_PATH=
# Relative path to the "base" library. It provides the core makefile build
# infrastructure.
CY_BASELIB_PATH=libs/psoc6make
# Absolute path to the compiler's "bin" directory.
#
# The default depends on the selected TOOLCHAIN (GCC_ARM uses the ModusToolbox
# IDE provided compiler by default).
CY_COMPILER_PATH=

And the new 2.2 Makefile where you can see the two new variables

  • CY_GETLIBS_SHARED_PATH=../
  • CY_GETLIBS_SHARED_NAME=mtb_shared
################################################################################
# Paths
################################################################################
# Relative path to the project directory (default is the Makefile's directory).
#
# This controls where automatic source code discovery looks for code.
CY_APP_PATH=
# Relative path to the shared repo location.
#
# All .mtb files have the format, <URI>#<COMMIT>#<LOCATION>. If the <LOCATION> field 
# begins with $$ASSET_REPO$$, then the repo is deposited in the path specified by 
# the CY_GETLIBS_SHARED_PATH variable. The default location is one directory level 
# above the current app directory.
# This is used with CY_GETLIBS_SHARED_NAME variable, which specifies the directory name.
CY_GETLIBS_SHARED_PATH=../
# Directory name of the shared repo location.
#
CY_GETLIBS_SHARED_NAME=mtb_shared

Dependencies & Libraries

The next thing to do is look at what libraries are in my original project.  To do this run the Library manager via “make modlibs”

All of these libraries are added to the project by creating “dot Libs” in the “deps” directory.  Specifically as I worked on the original project I added the libraries using the library manager, which created a dot lib for each library.  Here is a look at the original “deps” directory.

arh (master) TiltHydrometer $ ls deps
CY8CKIT-028-TFT.lib		abstraction-rtos.lib		display-tft-st7789v.lib		middleware-ntshell.lib		sensor-motion-bmi160.lib
TARGET_CY8CKIT-062S2-43012.lib	audio-codec-ak4954a.lib		emwin.lib			retarget-io.lib
TARGET_CY8CPROTO-062-4343W.lib	bluetooth-freertos.lib		freertos.lib			sensor-light.lib

When I did an “update” in Modus Toolbox 2.1 it ran “make depend” which brought in the libraries specified by the the “dot libs” into the “libs” directory.  Here it is a listing of the lib directory.

arh (master) TiltHydrometer $ ls libs
TARGET_CY8CKIT-062S2-43012	btstack.mtb			core-lib.mtb			mtb-hal-cat1.mtb		psoc6make
TARGET_CY8CPROTO-062-4343W	capsense			core-make.mtb			mtb-pdl-cat1.mtb		psoc6pdl
abstraction-rtos		capsense.mtb			display-tft-st7789v		mtb.mk				recipe-make-cat1a.mtb
abstraction-rtos.mtb		clib-support			emwin				psoc6cm0p			retarget-io
bluetooth-freertos		clib-support.mtb		freertos			psoc6cm0p.mtb
btstack				core-lib			middleware-ntshell		psoc6hal
arh (master) TiltHydrometer $

Now take the dramatic step of:

  • rm deps/*
  • rm -rf libs

I decided that the easiest thing to do to create the “mtb” files was to run the library manager.  But, with nothing in the deps directory, the library manager doesn’t know what to do.

arh (master *) TiltHydrometer $ make modlibs
Tools Directory: /Applications/ModusToolbox/tools_2.2
/Applications/ModusToolbox/tools_2.2/make/startex.mk:380: *** Build support for the target device not found. Run "make getlibs" to ensure all required build and code dependencies are present..  Stop.
arh (master *) TiltHydrometer $ 

So, I copy the target file from the Simple22 project.

arh (master *) TiltHydrometer $ cp ../Simple22/deps/TARGET_CY8CKIT-062S2-43012.mtb deps
arh (master *) TiltHydrometer $ more deps/TARGET_CY8CKIT-062S2-43012.mtb 
https://github.com/cypresssemiconductorco/TARGET_CY8CKIT-062S2-43012#latest-v2.X#$$ASSET_REPO$$/TARGET_CY8CKIT-062S2-43012/latest-v2.X

I was sure that I would be able to run the library manager now.  But it gave me the bird.  At least it gave me the hint of running “make getlibs” first.  Which I do:

arh (master *) TiltHydrometer $ make modlibs
Tools Directory: /Applications/ModusToolbox/tools_2.2
/Applications/ModusToolbox/tools_2.2/make/startex.mk:380: *** Build support for the target device not found. Run "make getlibs" to ensure all required build and code dependencies are present..  Stop.
arh (master *) TiltHydrometer $ make getlibs
Tools Directory: /Applications/ModusToolbox/tools_2.2
==============================================================================
= Importing libraries =
==============================================================================
Git is git version 2.24.3 (Apple Git-128), found at /usr/bin/git
Resolving dependencies...
QNetworkReplyHttpImplPrivate::_q_startOperation was called more than once QUrl("https://github.com/iotexpert/mtb2-iotexpert-manifests/raw/master/iotexpert-super-manifest.xml")
/Users/arh/proj/TiltHydrometer/libs/capsense.mtb was added or updated
/Users/arh/proj/TiltHydrometer/libs/core-lib.mtb was added or updated
/Users/arh/proj/TiltHydrometer/libs/core-make.mtb was added or updated
/Users/arh/proj/TiltHydrometer/libs/mtb-hal-cat1.mtb was added or updated
/Users/arh/proj/TiltHydrometer/libs/mtb-pdl-cat1.mtb was added or updated
/Users/arh/proj/TiltHydrometer/libs/psoc6cm0p.mtb was added or updated
/Users/arh/proj/TiltHydrometer/libs/recipe-make-cat1a.mtb was added or updated
Dependencies resolved.
Searching application directory (.mtb)...
Found 8 .mtb file(s)
Processing file "/Users/arh/proj/TiltHydrometer/deps/TARGET_CY8CKIT-062S2-43012.mtb"
Processing file "/Users/arh/proj/TiltHydrometer/libs/capsense.mtb"
Processing file "/Users/arh/proj/TiltHydrometer/libs/core-lib.mtb"
Processing file "/Users/arh/proj/TiltHydrometer/libs/core-make.mtb"
Processing file "/Users/arh/proj/TiltHydrometer/libs/mtb-hal-cat1.mtb"
Processing file "/Users/arh/proj/TiltHydrometer/libs/mtb-pdl-cat1.mtb"
Processing file "/Users/arh/proj/TiltHydrometer/libs/psoc6cm0p.mtb"
Processing file "/Users/arh/proj/TiltHydrometer/libs/recipe-make-cat1a.mtb"
Libraries processed.
Created file "/Users/arh/proj/TiltHydrometer/libs/mtb.mk".
==============================================================================
= Import complete =
==============================================================================
arh (master *) TiltHydrometer $ 

Now when I run “make modlibs” I can click on all of the libraries that I used in my project.

Once I click go.  It brings in all of the needed libraries.  Look at the “deps” directory now.  We are rocking.

arh (master *) TiltHydrometer $ ls deps
TARGET_CY8CKIT-062S2-43012.mtb	display-tft-st7789v.mtb		freertos.mtb			retarget-io.mtb
bluetooth-freertos.mtb		emwin.mtb			middleware-ntshell.mtb
arh (master *) TiltHydrometer $ cat deps/*
https://github.com/cypresssemiconductorco/TARGET_CY8CKIT-062S2-43012#latest-v2.X#$$ASSET_REPO$$/TARGET_CY8CKIT-062S2-43012/latest-v2.X
https://github.com/cypresssemiconductorco/bluetooth-freertos#latest-v1.X#$$ASSET_REPO$$/bluetooth-freertos/latest-v1.X
https://github.com/cypresssemiconductorco/display-tft-st7789v#latest-v1.X#$$ASSET_REPO$$/display-tft-st7789v/latest-v1.X
https://github.com/cypresssemiconductorco/emwin#latest-v5.X#$$ASSET_REPO$$/emwin/latest-v5.X
https://github.com/cypresssemiconductorco/freertos#latest-v10.X#$$ASSET_REPO$$/freertos/latest-v10.X
https://github.com/iotexpert/middleware-ntshell#latest-v2.X#$$ASSET_REPO$$/middleware-ntshell/latest-v2.X
https://github.com/cypresssemiconductorco/retarget-io#latest-v1.X#$$ASSET_REPO$$/retarget-io/latest-v1.X
arh (master *) TiltHydrometer $

And when I look in the shared library directory which is “../mtb_shared” you can see all of the source files.

arh (master *) TiltHydrometer $ ls ../mtb_shared/
TARGET_CY8CKIT-062S2-43012	capsense			display-tft-st7789v		mtb-hal-cat1			retarget-io
abstraction-rtos		clib-support			emwin				mtb-pdl-cat1
bluetooth-freertos		core-lib			freertos			psoc6cm0p
btstack				core-make			middleware-ntshell		recipe-make-cat1a

Build and Test

All the libraries are now fixed.  So run a “make -j build” and see what we have.

arh (master *) TiltHydrometer $ make -j build
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: mtb-example-psoc6-empty-app Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 237 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 38 .a file(s)
-> Found 665 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 218 file(s)
Compiling app file app_bt_cfg.c
Compiling app file bluetoothManager.c
Compiling app file capsenseManager.c
.... a bunch of compile messages deleted....
Compiling ext file cy_wdt.c
Compiling ext file psoc6_01_cm0p_sleep.c
Compiling ext file psoc6_02_cm0p_sleep.c
Compiling ext file psoc6_03_cm0p_sleep.c
Compiling ext file psoc6_04_cm0p_sleep.c
Compiling ext file cy_retarget_io.c
Linking output file mtb-example-psoc6-empty-app.elf
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  322616     | 
| .ARM.exidx           |  0x10050c38   |  8          | 
| .copy.table          |  0x10050c40   |  24         | 
| .zero.table          |  0x10050c58   |  8          | 
| .data                |  0x080022e0   |  2544       | 
| .cy_sharedmem        |  0x08002cd0   |  8          | 
| .noinit              |  0x08002cd8   |  148        | 
| .bss                 |  0x08002d70   |  40048      | 
| .heap                |  0x0800c9e0   |  990752     | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           333408     
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033500    
arh (master *) TiltHydrometer $

A functioning project.  Sweet!

Kentucky Inspired Me … Thanks Erich

I follow Erich’s blog.  In the last few years he has been posting pictures taken near his home in Switzerland.  Remarkable.

In the last few days Kentucky has inspired to take some fall pictures.  I will not pretend they are great works of photography.  But I think that they are pretty.

How are they IoT?  They aren’t.


ModusToolbox 2.2 Template Project – FreeRTOS + NTShell

Summary

This article discusses the new library structure that was released with ModusToolbox 2.2.  I explain it by showing the creation of a template project that use FreeRTOS and NT Shell.

Story

I have often started projects from the IoT Expert FreeRTOS template project.   I realized the other day that almost always the first thing I do after creating the project is add the NT Shell library.  My friend Hassane has a personal mantra that if he is going to do the same job more than once he will always automate it.  I should have listened to him on this one because I have done it a bunch of times.

In Modus Toolbox 2.2 we have created a new library scheme which allows sharing of libraries between projects.  So this will also be a good example of how that works.

This will also give you another example of adding template projects to your own manifest.

Here is what I am going to do:

  1. Create a project from the IoT Expert FreeRTOS Template
  2. Add the NTShell Library & Examine New Library Structure
  3. Update the Project and Program
  4. Add the Task List functionality (a nice feature of FreeRTOS)
  5. Put the new template on GitHub
  6. Update the IoT Expert App Manifest
  7. Test the new Template

Create & Test a project from the IoT Expert FreeRTOS Template

I will start the whole process by creating  new project using my existing base template.  The kit that I happen to have on my desk right now is the CY8CKIT-062S2-43012.

Select the IoT Expert FreeRTOS Template and give it a name.  Notice that I add “NTShell” to the name (because that is what Im gonna add)

When you click create, Modus will do its magic and build you a complete project.

Today Im going to edit using Visual Studio Code.  Actually almost always I edit using Visual Studio Code.  You can do all of these tasks using Eclipse as well.  To turn my created project into a VSCODE project run “make vscode”

Before getting to far down the road I like to run a build to make sure everything is OK.  So “make -j build”

arh (master) IoT_Expert_FreeRTOS_NTShell_Template $ make -j build
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 195 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 491 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 183 file(s)
Compiling app file lowPower.c
Compiling app file main.c
Compiling ext file startup_psoc6_02_cm4.S
Compiling ext file cy_syslib_gcc.S
Compiling ext file cycfg.c
Compiling ext file cycfg_capsense.c
Compiling ext file cycfg_clocks.c
....a bunch of stuff deleted
Compiling ext file psoc6_04_cm0p_sleep.c
Compiling ext file cy_retarget_io.c
Linking output file MTBShellTemplate.elf
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  30280      | 
| .ARM.exidx           |  0x10009648   |  8          | 
| .copy.table          |  0x10009650   |  24         | 
| .zero.table          |  0x10009668   |  8          | 
| .data                |  0x080022e0   |  1320       | 
| .cy_sharedmem        |  0x08002808   |  8          | 
| .noinit              |  0x08002810   |  148        | 
| .bss                 |  0x080028a4   |  1324       | 
| .heap                |  0x08002dd0   |  1030704    | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           39848      
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033504    
arh (master) IoT_Expert_FreeRTOS_NTShell_Template $

Then program it, just to make sure.  “make program”

arh (master) IoT_Expert_FreeRTOS_NTShell_Template $ make program
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 195 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 491 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 183 file(s)
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  30280      | 
| .ARM.exidx           |  0x10009648   |  8          | 
| .copy.table          |  0x10009650   |  24         | 
| .zero.table          |  0x10009668   |  8          | 
| .data                |  0x080022e0   |  1320       | 
| .cy_sharedmem        |  0x08002808   |  8          | 
| .noinit              |  0x08002810   |  148        | 
| .bss                 |  0x080028a4   |  1324       | 
| .heap                |  0x08002dd0   |  1030704    | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           39848      
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033504    
Programming target device... 
Open On-Chip Debugger 0.10.0+dev-4.1.0.1058 (2020-08-11-03:45)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
adapter speed: 2000 kHz
adapter srst delay: 25
adapter srst pulse_width: 25
** Auto-acquire enabled, use "set ENABLE_ACQUIRE 0" to disable
cortex_m reset_config sysresetreq
cortex_m reset_config sysresetreq
Info : Using CMSIS loader 'CY8C6xxA_SMIF' for bank 'psoc6_smif0_cm0' (footprint 14672 bytes)
Warn : SFlash programming allowed for regions: USER, TOC, KEY
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : KitProg3: FW version: 1.14.514
Info : KitProg3: Pipelined transfers disabled, please update the firmware
Info : VTarget = 3.221 V
Info : kitprog3: acquiring the device...
Info : clock speed 2000 kHz
Info : SWD DPIDR 0x6ba02477
Info : psoc6.cpu.cm0: hardware has 4 breakpoints, 2 watchpoints
***************************************
** Silicon: 0xE453, Family: 0x102, Rev.: 0x12 (A1)
** Detected Device: CY8C624ABZI-S2D44
** Detected Main Flash size, kb: 2048
** Flash Boot version: 3.1.0.378
** Chip Protection: NORMAL
***************************************
Info : psoc6.cpu.cm4: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for psoc6.cpu.cm0 on 3333
Info : Listening on port 3333 for gdb connections
Info : starting gdb server for psoc6.cpu.cm4 on 3334
Info : Listening on port 3334 for gdb connections
Info : SWD DPIDR 0x6ba02477
Info : kitprog3: acquiring the device...
psoc6.cpu.cm0 halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x00000190 msp: 0x080ff800
** Device acquired successfully
** psoc6.cpu.cm4: Ran after reset and before halt...
psoc6.cpu.cm4 halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0000012a msp: 0x080ff800
** Programming Started **
auto erase enabled
Info : Flash write discontinued at 0x1000179c, next section at 0x10002000
Info : Padding image section 0 at 0x1000179c with 100 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
Info : Padding image section 1 at 0x10009ba0 with 96 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
wrote 37888 bytes from file /Users/arh/mtb22/IoT_Expert_FreeRTOS_NTShell_Template/build/CY8CKIT-062S2-43012/Debug/MTBShellTemplate.hex in 1.402638s (26.379 KiB/s)
** Programming Finished **
** Verify Started **
verified 37692 bytes in 0.080973s (454.579 KiB/s)
** Verified OK **
** Resetting Target **
Info : SWD DPIDR 0x6ba02477
shutdown command invoked
Info : psoc6.dap: powering down debug domain...
Warn : Failed to power down Debug Domains
arh (master) IoT_Expert_FreeRTOS_NTShell_Template $

Add the NTShell Library & Examine New Library Structure

Everything is working and my basic project has FreeRTOS and a blinking LED.  Now let’s add the NT Shell Library.  To do this run the library manager by running “make modlibs” (or click on the button in Eclipse).  Select Libraries –> IoT Expert –> ntshell

When you press update, the library manager will do its thing again.

When I look in the “deps” directory, I see some new file types called “.mtb”.  These files tell your project where to find each of the libraries.  Notice that the middleware-ntshell.mtb points to “$$ASSET_REPO$$”.  Where is that?

If you have a aook at the Makefile it tell you that it is “../” and “mtb_shared”.

# Relative path to the shared repo location.
#
# All .mtb files have the format, <URI><COMMIT><LOCATION>. If the <LOCATION> field 
# begins with $$ASSET_REPO$$, then the repo is deposited in the path specified by 
# the CY_GETLIBS_SHARED_PATH variable. The default location is one directory level 
# above the current app directory.
# This is used with CY_GETLIBS_SHARED_NAME variable, which specifies the directory name.
CY_GETLIBS_SHARED_PATH=../
# Directory name of the shared repo location.
#
CY_GETLIBS_SHARED_NAME=mtb_shared

To start editing I will run Visual Studio Code by typing “code .”.  The first thing that happens is that it notices that this is a workspace instead of just a directory.

And when you look at the workspace you can see that it knows about both the example project as well as “mtb_shared”.  That is sweet.

Update the Project and Program

Now follow the instructions from the middlware-ntshell readme by copying “usrcmd.*” into my project.

arh (master) IoT_Expert_FreeRTOS_NTShell_Template $ cp ../mtb_shared/middleware-ntshell/latest-v2.X/template/psoc6sdk/usrcmd.* .

Then I edit main.c to

  1. #include “usrcmd.h” on line 8
  2. Start the shell task which is called “usrcmd_task” on line 39

Now buid/project by running “make -j program”

arh (master *) IoT_Expert_FreeRTOS_NTShell_Template $ make -j program
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 205 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 503 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 193 file(s)
Compiling app file main.c
Linking output file MTBShellTemplate.elf
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  52780      | 
| .ARM.exidx           |  0x1000ee2c   |  8          | 
| .copy.table          |  0x1000ee34   |  24         | 
| .zero.table          |  0x1000ee4c   |  8          | 
| .data                |  0x080022e0   |  1688       | 
| .cy_sharedmem        |  0x08002978   |  8          | 
| .noinit              |  0x08002980   |  148        | 
| .bss                 |  0x08002a14   |  2136       | 
| .heap                |  0x08003270   |  1029520    | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           62716      
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033500    
Programming target device... 
Open On-Chip Debugger 0.10.0+dev-4.1.0.1058 (2020-08-11-03:45)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
adapter speed: 2000 kHz
adapter srst delay: 25
adapter srst pulse_width: 25
** Auto-acquire enabled, use "set ENABLE_ACQUIRE 0" to disable
cortex_m reset_config sysresetreq
cortex_m reset_config sysresetreq
Info : Using CMSIS loader 'CY8C6xxA_SMIF' for bank 'psoc6_smif0_cm0' (footprint 14672 bytes)
Warn : SFlash programming allowed for regions: USER, TOC, KEY
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : KitProg3: FW version: 1.14.514
Info : KitProg3: Pipelined transfers disabled, please update the firmware
Info : VTarget = 3.225 V
Info : kitprog3: acquiring the device...
Info : clock speed 2000 kHz
Info : SWD DPIDR 0x6ba02477
Info : psoc6.cpu.cm0: hardware has 4 breakpoints, 2 watchpoints
***************************************
** Silicon: 0xE453, Family: 0x102, Rev.: 0x12 (A1)
** Detected Device: CY8C624ABZI-S2D44
** Detected Main Flash size, kb: 2048
** Flash Boot version: 3.1.0.378
** Chip Protection: NORMAL
***************************************
Info : psoc6.cpu.cm4: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for psoc6.cpu.cm0 on 3333
Info : Listening on port 3333 for gdb connections
Info : starting gdb server for psoc6.cpu.cm4 on 3334
Info : Listening on port 3334 for gdb connections
Info : SWD DPIDR 0x6ba02477
Info : kitprog3: acquiring the device...
psoc6.cpu.cm0 halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x00000190 msp: 0x080ff800
** Device acquired successfully
** psoc6.cpu.cm4: Ran after reset and before halt...
psoc6.cpu.cm4 halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0000012a msp: 0x080ff800
** Programming Started **
auto erase enabled
Info : Flash write discontinued at 0x1000179c, next section at 0x10002000
Info : Padding image section 0 at 0x1000179c with 100 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
Info : Padding image section 1 at 0x1000f4f4 with 268 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
wrote 60928 bytes from file /Users/arh/mtb22/IoT_Expert_FreeRTOS_NTShell_Template/build/CY8CKIT-062S2-43012/Debug/MTBShellTemplate.hex in 2.017097s (29.498 KiB/s)
** Programming Finished **
** Verify Started **
verified 60560 bytes in 0.119193s (496.175 KiB/s)
** Verified OK **
** Resetting Target **
Info : SWD DPIDR 0x6ba02477
shutdown command invoked
Info : psoc6.dap: powering down debug domain...
arh (master *) IoT_Expert_FreeRTOS_NTShell_Template $

Now I have a functional shell.  Here is the serial console.

Add the Task List functionality

In the main.c above I started up the usrcmd_task with a stack size of config_MINIMAL_STACK_SIZE * 4.  Where did I get that?  Well the first couple of times I did this it crashed by running out of stack so I tried bigger and bigger numbers until it stopped crashing.  This is a kind of a pain in the ass.   If you know FreeRTOS there is a function called “vTaskList” which will give you stats about the tasks.

In order to use that function you need to turn it on.  Notice that I #ifdef and #if to see if it is turned on inside of usrcmd.c

#ifdef configUSE_TRACE_FACILITY
#if configUSE_STATS_FORMATTING_FUNCTIONS ==1
static int usrcmd_list(int argc, char **argv);
#endif
#endif

So let’s turn it on by editing “FreeRTOSConfig.h” and enabling the two required defines.

#define configUSE_TRACE_FACILITY					1
#define configUSE_STATS_FORMATTING_FUNCTIONS        1

Now build/program.

arh (master *) IoT_Expert_FreeRTOS_NTShell_Template $ make -j program
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 205 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 503 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 193 file(s)
Compiling app file lowPower.c
Compiling app file main.c
Compiling app file usrcmd.c
Compiling ext file croutine.c
Compiling ext file event_groups.c
Compiling ext file list.c
Compiling ext file heap_1.c
Compiling ext file heap_2.c
Compiling ext file heap_3.c
Compiling ext file heap_4.c
Compiling ext file heap_5.c
Compiling ext file port.c
Compiling ext file queue.c
Compiling ext file stream_buffer.c
Compiling ext file tasks.c
Compiling ext file timers.c
Compiling ext file psoc6_ntshell_port.c
Linking output file MTBShellTemplate.elf
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  54876      | 
| .ARM.exidx           |  0x1000f65c   |  8          | 
| .copy.table          |  0x1000f664   |  24         | 
| .zero.table          |  0x1000f67c   |  8          | 
| .data                |  0x080022e0   |  1688       | 
| .cy_sharedmem        |  0x08002978   |  8          | 
| .noinit              |  0x08002980   |  148        | 
| .bss                 |  0x08002a14   |  2136       | 
| .heap                |  0x08003270   |  1029520    | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           64812      
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033500    
Programming target device... 
Open On-Chip Debugger 0.10.0+dev-4.1.0.1058 (2020-08-11-03:45)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
adapter speed: 2000 kHz
adapter srst delay: 25
adapter srst pulse_width: 25
** Auto-acquire enabled, use "set ENABLE_ACQUIRE 0" to disable
cortex_m reset_config sysresetreq
cortex_m reset_config sysresetreq
Info : Using CMSIS loader 'CY8C6xxA_SMIF' for bank 'psoc6_smif0_cm0' (footprint 14672 bytes)
Warn : SFlash programming allowed for regions: USER, TOC, KEY
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : KitProg3: FW version: 1.14.514
Info : KitProg3: Pipelined transfers disabled, please update the firmware
Info : VTarget = 3.220 V
Info : kitprog3: acquiring the device...
Info : clock speed 2000 kHz
Info : SWD DPIDR 0x6ba02477
Info : psoc6.cpu.cm0: hardware has 4 breakpoints, 2 watchpoints
***************************************
** Silicon: 0xE453, Family: 0x102, Rev.: 0x12 (A1)
** Detected Device: CY8C624ABZI-S2D44
** Detected Main Flash size, kb: 2048
** Flash Boot version: 3.1.0.378
** Chip Protection: NORMAL
***************************************
Info : psoc6.cpu.cm4: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for psoc6.cpu.cm0 on 3333
Info : Listening on port 3333 for gdb connections
Info : starting gdb server for psoc6.cpu.cm4 on 3334
Info : Listening on port 3334 for gdb connections
Info : SWD DPIDR 0x6ba02477
Info : kitprog3: acquiring the device...
psoc6.cpu.cm0 halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x00000190 msp: 0x080ff800
** Device acquired successfully
** psoc6.cpu.cm4: Ran after reset and before halt...
psoc6.cpu.cm4 halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0000012a msp: 0x080ff800
** Programming Started **
auto erase enabled
Info : Flash write discontinued at 0x1000179c, next section at 0x10002000
Info : Padding image section 0 at 0x1000179c with 100 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
Info : Padding image section 1 at 0x1000fd24 with 220 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
wrote 62976 bytes from file /Users/arh/mtb22/IoT_Expert_FreeRTOS_NTShell_Template/build/CY8CKIT-062S2-43012/Debug/MTBShellTemplate.hex in 2.092903s (29.385 KiB/s)
** Programming Finished **
** Verify Started **
verified 62656 bytes in 0.123619s (494.968 KiB/s)
** Verified OK **
** Resetting Target **
Info : SWD DPIDR 0x6ba02477
shutdown command invoked
Info : psoc6.dap: powering down debug domain...

When I run help you can see I have a new command called “tasks” which lists all of the tasks and their stack high water marks.

Put the Template on GitHub

I am happy with my new template.  So, I go to GitHub and create a new repository.

Then on my current project:

  1. I blow away the git history (didnt really have to do that).
  2. Create a new git repo “git init .”
  3. Add a pointer to GitHub “git remote add….”
  4. Add all of the files “git add *”
  5. Add the .gitignore “git add .gitignore”
  6. Commit the changes “git commit…”
  7. Push it to GitHub “git push …”
arh (master *) IoT_Expert_FreeRTOS_NTShell_Template $ rm -rf .git
arh IoT_Expert_FreeRTOS_NTShell_Template $ git init .
Initialized empty Git repository in /Users/arh/mtb22/IoT_Expert_FreeRTOS_NTShell_Template/.git/
arh (master #) IoT_Expert_FreeRTOS_NTShell_Template $ git remote add origin git@iotexpert.github.com:iotexpert/mtb2-freertos-ntshell-template.git
arh (master #) IoT_Expert_FreeRTOS_NTShell_Template $ git add * 
The following paths are ignored by one of your .gitignore files:
build
Use -f if you really want to add them.
arh (master #) IoT_Expert_FreeRTOS_NTShell_Template $ git add .gitignore
arh (master #) IoT_Expert_FreeRTOS_NTShell_Template $ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file:   .gitignore
new file:   FreeRTOSConfig.h
new file:   LICENSE
new file:   MTBShellTemplate.code-workspace
new file:   Makefile
new file:   README.md
new file:   deps/TARGET_CY8CKIT-062S2-43012.mtb
new file:   deps/freertos.mtb
new file:   deps/middleware-ntshell.mtb
new file:   deps/retarget-io.mtb
new file:   global.h
new file:   lowPower.c
new file:   main.c
new file:   openocd.tcl
new file:   usrcmd.c
new file:   usrcmd.h
Untracked files:
(use "git add <file>..." to include in what will be committed)
.vscode/
arh (master #) IoT_Expert_FreeRTOS_NTShell_Template $ git commit -m "Initial commit"
[master (root-commit) 26b5d3c] Initial commit
16 files changed, 994 insertions(+)
create mode 100644 .gitignore
create mode 100644 FreeRTOSConfig.h
create mode 100644 LICENSE
create mode 100644 MTBShellTemplate.code-workspace
create mode 100644 Makefile
create mode 100644 README.md
create mode 100644 deps/TARGET_CY8CKIT-062S2-43012.mtb
create mode 100644 deps/freertos.mtb
create mode 100644 deps/middleware-ntshell.mtb
create mode 100644 deps/retarget-io.mtb
create mode 100644 global.h
create mode 100644 lowPower.c
create mode 100644 main.c
create mode 100644 openocd.tcl
create mode 100644 usrcmd.c
create mode 100644 usrcmd.h
arh (master) IoT_Expert_FreeRTOS_NTShell_Template $ git push -u origin master
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 12 threads
Compressing objects: 100% (19/19), done.
Writing objects: 100% (19/19), 16.21 KiB | 5.40 MiB/s, done.
Total 19 (delta 0), reused 0 (delta 0)
To iotexpert.github.com:iotexpert/mtb2-freertos-ntshell-template.git
* [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
arh (master) IoT_Expert_FreeRTOS_NTShell_Template $

Update the Manifests

To get access to the new template I need to add it to the IoT Expert App Manifest.  I edit the xml file to have the new app

<apps>
<app>
<name>IoT Expert FreeRTOS Template</name>
<id>mtb2-freertos-template</id>
<uri>https://github.com/iotexpert/mtb2-freertos-template</uri>
<description>This template provide a starting point for FreeRTOS projects.  Including a starting blinking LED task</description>
<req_capabilities>psoc6</req_capabilities>
<versions>
<version>
<num>Latest 1.X release</num>
<commit>master</commit>
</version>
</versions>
</app>
<app>
<name>IoT Expert FreeRTOS NTShell Template</name>
<id>mtb2-freertos-ntshell-template</id>
<uri>https://github.com/iotexpert/mtb2-freertos-ntshell-template</uri>
<description>This template provide a starting point for FreeRTOS projects.  Including a starting blinking LED task and shell</description>
<req_capabilities>psoc6</req_capabilities>
<versions>
<version>
<num>Latest 1.X release</num>
<commit>master</commit>
</version>
</versions>
</app>
</apps>

Now I need to git add, git commit and git push it to GitHub.

arh (master) mtb2-iotexpert-manifests $ code .
arh (master) mtb2-iotexpert-manifests $ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified:   iotexpert-app-manifest.xml
no changes added to commit (use "git add" and/or "git commit -a")
arh (master *) mtb2-iotexpert-manifests $ git add iotexpert-app-manifest.xml
arh (master +) mtb2-iotexpert-manifests $ git commit -m "Updated with ntshell example"
[master 47a7bb1] Updated with ntshell example
1 file changed, 13 insertions(+)
arh (master) mtb2-iotexpert-manifests $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 554 bytes | 554.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To iotexpert.github.com:iotexpert/mtb2-iotexpert-manifests.git
28ed2d0..47a7bb1  master -> master

Test the new Template

Everything should be working so make a new project.

Cool.  There is the new template.

When I program it… everything is cool.

arh (master) TestNTS $ make -j program
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Prebuild operations complete
Commencing build operations...
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk
Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM
Auto-discovery in progress...
-> Found 205 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 503 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete
Constructing build rules...
Build rules construction complete
==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 193 file(s)
Compiling app file lowPower.c
Compiling app file main.c
Compiling app file usrcmd.c
Compiling ext file startup_psoc6_02_cm4.S
Compiling ext file cy_syslib_gcc.S
Compiling ext file cycfg.c
Compiling ext file cycfg_capsense.c
Compiling ext file cycfg_clocks.c
Compiling ext file cycfg_peripherals.c
Compiling ext file cycfg_pins.c
Compiling ext file cycfg_qspi_memslot.c
Compiling ext file cycfg_routing.c
Compiling ext file cycfg_system.c
Compiling ext file system_psoc6_cm4.c
Compiling ext file cybsp.c
Compiling ext file cy_capsense_centroid.c
Compiling ext file cy_capsense_control.c
Compiling ext file cy_capsense_csd.c
Compiling ext file cy_capsense_csx.c
Compiling ext file cy_capsense_filter.c
Compiling ext file cy_capsense_processing.c
Compiling ext file cy_capsense_selftest.c
Compiling ext file cy_capsense_sensing.c
Compiling ext file cy_capsense_structure.c
Compiling ext file cy_capsense_tuner.c
Compiling ext file croutine.c
Compiling ext file event_groups.c
Compiling ext file list.c
Compiling ext file heap_1.c
Compiling ext file heap_2.c
Compiling ext file heap_3.c
Compiling ext file heap_4.c
Compiling ext file heap_5.c
Compiling ext file port.c
Compiling ext file queue.c
Compiling ext file stream_buffer.c
Compiling ext file tasks.c
Compiling ext file timers.c
Compiling ext file ntlibc.c
Compiling ext file ntshell.c
Compiling ext file text_editor.c
Compiling ext file text_history.c
Compiling ext file vtrecv.c
Compiling ext file vtsend.c
Compiling ext file psoc6_ntshell_port.c
Compiling ext file ntopt.c
Compiling ext file ntstdio.c
Compiling ext file cyhal_adc.c
Compiling ext file cyhal_analog_common.c
Compiling ext file cyhal_clock.c
Compiling ext file cyhal_comp.c
Compiling ext file cyhal_comp_ctb.c
Compiling ext file cyhal_comp_lp.c
Compiling ext file cyhal_crc.c
Compiling ext file cyhal_crypto_common.c
Compiling ext file cyhal_dac.c
Compiling ext file cyhal_deprecated.c
Compiling ext file cyhal_dma.c
Compiling ext file cyhal_dma_dmac.c
Compiling ext file cyhal_dma_dw.c
Compiling ext file cyhal_ezi2c.c
Compiling ext file cyhal_flash.c
Compiling ext file cyhal_gpio.c
Compiling ext file cyhal_hwmgr.c
Compiling ext file cyhal_i2c.c
Compiling ext file cyhal_i2s.c
Compiling ext file cyhal_interconnect.c
Compiling ext file cyhal_lptimer.c
Compiling ext file cyhal_not_implemented.c
Compiling ext file cyhal_opamp.c
Compiling ext file cyhal_pdmpcm.c
Compiling ext file cyhal_pwm.c
Compiling ext file cyhal_qspi.c
Compiling ext file cyhal_rtc.c
Compiling ext file cyhal_scb_common.c
Compiling ext file cyhal_sdhc.c
Compiling ext file cyhal_spi.c
Compiling ext file cyhal_syspm.c
Compiling ext file cyhal_system.c
Compiling ext file cyhal_tcpwm_common.c
Compiling ext file cyhal_timer.c
Compiling ext file cyhal_trng.c
Compiling ext file cyhal_uart.c
Compiling ext file cyhal_udb_sdio.c
Compiling ext file cyhal_usb_dev.c
Compiling ext file cyhal_utils.c
Compiling ext file cyhal_wdt.c
Compiling ext file cyhal_psoc6_01_104_m_csp_ble.c
Compiling ext file cyhal_psoc6_01_104_m_csp_ble_usb.c
Compiling ext file cyhal_psoc6_01_116_bga_ble.c
Compiling ext file cyhal_psoc6_01_116_bga_usb.c
Compiling ext file cyhal_psoc6_01_124_bga.c
Compiling ext file cyhal_psoc6_01_124_bga_sip.c
Compiling ext file cyhal_psoc6_01_43_smt.c
Compiling ext file cyhal_psoc6_01_68_qfn_ble.c
Compiling ext file cyhal_psoc6_01_80_wlcsp.c
Compiling ext file cyhal_psoc6_02_100_wlcsp.c
Compiling ext file cyhal_psoc6_02_124_bga.c
Compiling ext file cyhal_psoc6_02_128_tqfp.c
Compiling ext file cyhal_psoc6_02_68_qfn.c
Compiling ext file cyhal_psoc6_03_100_tqfp.c
Compiling ext file cyhal_psoc6_03_49_wlcsp.c
Compiling ext file cyhal_psoc6_03_68_qfn.c
Compiling ext file cyhal_psoc6_04_64_tqfp.c
Compiling ext file cyhal_psoc6_04_68_qfn.c
Compiling ext file cyhal_psoc6_04_80_tqfp.c
Compiling ext file cyhal_triggers_psoc6_01.c
Compiling ext file cyhal_triggers_psoc6_02.c
Compiling ext file cyhal_triggers_psoc6_03.c
Compiling ext file cyhal_triggers_psoc6_04.c
Compiling ext file cy_ble_clk.c
Compiling ext file cy_canfd.c
Compiling ext file cy_crypto.c
Compiling ext file cy_crypto_core_aes_v1.c
Compiling ext file cy_crypto_core_aes_v2.c
Compiling ext file cy_crypto_core_cmac_v1.c
Compiling ext file cy_crypto_core_cmac_v2.c
Compiling ext file cy_crypto_core_crc_v1.c
Compiling ext file cy_crypto_core_crc_v2.c
Compiling ext file cy_crypto_core_des_v1.c
Compiling ext file cy_crypto_core_des_v2.c
Compiling ext file cy_crypto_core_ecc_domain_params.c
Compiling ext file cy_crypto_core_ecc_ecdsa.c
Compiling ext file cy_crypto_core_ecc_key_gen.c
Compiling ext file cy_crypto_core_ecc_nist_p.c
Compiling ext file cy_crypto_core_hmac_v1.c
Compiling ext file cy_crypto_core_hmac_v2.c
Compiling ext file cy_crypto_core_hw.c
Compiling ext file cy_crypto_core_hw_v1.c
Compiling ext file cy_crypto_core_mem_v1.c
Compiling ext file cy_crypto_core_mem_v2.c
Compiling ext file cy_crypto_core_prng_v1.c
Compiling ext file cy_crypto_core_prng_v2.c
Compiling ext file cy_crypto_core_rsa.c
Compiling ext file cy_crypto_core_sha_v1.c
Compiling ext file cy_crypto_core_sha_v2.c
Compiling ext file cy_crypto_core_trng_v1.c
Compiling ext file cy_crypto_core_trng_v2.c
Compiling ext file cy_crypto_core_vu.c
Compiling ext file cy_crypto_server.c
Compiling ext file cy_csd.c
Compiling ext file cy_ctb.c
Compiling ext file cy_ctdac.c
Compiling ext file cy_device.c
Compiling ext file cy_dma.c
Compiling ext file cy_dmac.c
Compiling ext file cy_efuse.c
Compiling ext file cy_flash.c
Compiling ext file cy_gpio.c
Compiling ext file cy_i2s.c
Compiling ext file cy_ipc_drv.c
Compiling ext file cy_ipc_pipe.c
Compiling ext file cy_ipc_sema.c
Compiling ext file cy_lpcomp.c
Compiling ext file cy_lvd.c
Compiling ext file cy_mcwdt.c
Compiling ext file cy_pdm_pcm.c
Compiling ext file cy_pra.c
Compiling ext file cy_pra_cfg.c
Compiling ext file cy_profile.c
Compiling ext file cy_prot.c
Compiling ext file cy_rtc.c
Compiling ext file cy_sar.c
Compiling ext file cy_scb_common.c
Compiling ext file cy_scb_ezi2c.c
Compiling ext file cy_scb_i2c.c
Compiling ext file cy_scb_spi.c
Compiling ext file cy_scb_uart.c
Compiling ext file cy_sd_host.c
Compiling ext file cy_seglcd.c
Compiling ext file cy_smartio.c
Compiling ext file cy_smif.c
Compiling ext file cy_smif_memslot.c
Compiling ext file cy_sysanalog.c
Compiling ext file cy_sysclk.c
Compiling ext file cy_sysint.c
Compiling ext file cy_syslib.c
Compiling ext file cy_syspm.c
Compiling ext file cy_systick.c
Compiling ext file cy_tcpwm_counter.c
Compiling ext file cy_tcpwm_pwm.c
Compiling ext file cy_tcpwm_quaddec.c
Compiling ext file cy_tcpwm_shiftreg.c
Compiling ext file cy_trigmux.c
Compiling ext file cy_usbfs_dev_drv.c
Compiling ext file cy_usbfs_dev_drv_io.c
Compiling ext file cy_usbfs_dev_drv_io_dma.c
Compiling ext file cy_wdt.c
Compiling ext file psoc6_01_cm0p_sleep.c
Compiling ext file psoc6_02_cm0p_sleep.c
Compiling ext file psoc6_03_cm0p_sleep.c
Compiling ext file psoc6_04_cm0p_sleep.c
Compiling ext file cy_retarget_io.c
Linking output file MTBShellTemplate.elf
==============================================================================
= Build complete =
==============================================================================
Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og
---------------------------------------------------- 
| Section Name         |  Address      |  Size       | 
---------------------------------------------------- 
| .cy_m0p_image        |  0x10000000   |  6044       | 
| .text                |  0x10002000   |  54876      | 
| .ARM.exidx           |  0x1000f65c   |  8          | 
| .copy.table          |  0x1000f664   |  24         | 
| .zero.table          |  0x1000f67c   |  8          | 
| .data                |  0x080022e0   |  1688       | 
| .cy_sharedmem        |  0x08002978   |  8          | 
| .noinit              |  0x08002980   |  148        | 
| .bss                 |  0x08002a14   |  2136       | 
| .heap                |  0x08003270   |  1029520    | 
---------------------------------------------------- 
Total Internal Flash (Available)          2097152    
Total Internal Flash (Utilized)           64812      
Total Internal SRAM (Available)           1046528    
Total Internal SRAM (Utilized with heap)  1033500    
Programming target device... 
Open On-Chip Debugger 0.10.0+dev-4.1.0.1058 (2020-08-11-03:45)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
adapter speed: 2000 kHz
adapter srst delay: 25
adapter srst pulse_width: 25
** Auto-acquire enabled, use "set ENABLE_ACQUIRE 0" to disable
cortex_m reset_config sysresetreq
cortex_m reset_config sysresetreq
Info : Using CMSIS loader 'CY8C6xxA_SMIF' for bank 'psoc6_smif0_cm0' (footprint 14672 bytes)
Warn : SFlash programming allowed for regions: USER, TOC, KEY
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : KitProg3: FW version: 1.14.514
Info : KitProg3: Pipelined transfers disabled, please update the firmware
Info : VTarget = 3.221 V
Info : kitprog3: acquiring the device...
Info : clock speed 2000 kHz
Info : SWD DPIDR 0x6ba02477
Info : psoc6.cpu.cm0: hardware has 4 breakpoints, 2 watchpoints
***************************************
** Silicon: 0xE453, Family: 0x102, Rev.: 0x12 (A1)
** Detected Device: CY8C624ABZI-S2D44
** Detected Main Flash size, kb: 2048
** Flash Boot version: 3.1.0.378
** Chip Protection: NORMAL
***************************************
Info : psoc6.cpu.cm4: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for psoc6.cpu.cm0 on 3333
Info : Listening on port 3333 for gdb connections
Info : starting gdb server for psoc6.cpu.cm4 on 3334
Info : Listening on port 3334 for gdb connections
Info : SWD DPIDR 0x6ba02477
Info : kitprog3: acquiring the device...
psoc6.cpu.cm0 halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x00000190 msp: 0x080ff800
** Device acquired successfully
** psoc6.cpu.cm4: Ran after reset and before halt...
psoc6.cpu.cm4 halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0000012a msp: 0x080ff800
** Programming Started **
auto erase enabled
Info : Flash write discontinued at 0x1000179c, next section at 0x10002000
Info : Padding image section 0 at 0x1000179c with 100 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
Info : Padding image section 1 at 0x1000fd24 with 220 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
wrote 62976 bytes from file /Users/arh/mtb22/TestNTS/build/CY8CKIT-062S2-43012/Debug/MTBShellTemplate.hex in 2.082329s (29.534 KiB/s)
** Programming Finished **
** Verify Started **
verified 62656 bytes in 0.122516s (499.425 KiB/s)
** Verified OK **
** Resetting Target **
Info : SWD DPIDR 0x6ba02477
shutdown command invoked
Info : psoc6.dap: powering down debug domain...
arh (master) TestNTS $

And the project seems to be doing the needful.

The Wires Are Out Of Control

You have to start by admitting you have a problem

OK, it looks like I’m on the right track.

But, that made things worse in other ways.

Really?

Let’s make some progress on the power.

Having a lab assistant is awesome

Finally…

You can see:

  1. My new work Windows laptop
  2. My personal Mac
  3. A 4K display that is switched with a KVM under the desk
  4. The Jetson Nano that I am currently working on
  5. The Thunderbolt hub connected to my Mac
  6. A beautiful antique magnifying glass
  7. A Weller soldering iron
  8. An amazing Leica microscope
  9. My Linux box (under the desk).  It is attached via display port to the monitor – but it is mostly logged into via the network
  10. A paper protractor that my daughter made when she was little
  11. A paper Go Dad flag made by my daughter when she was little
  12. UK (BSEE) & Georgia Tech (MSEE) Diplomas
  13. Part of my photojournalism portfolio
  14.  My new work phone on the wireless charger
  15. A pack of post-it-notes
  16. A paper tracing of my Katana done with the NBTHK Hozon