PSoC 6 BLE – Find Me Profile (Target)

Summary

I have been working on making some new videos for Cypress about the PSoC 6 BLE.  Everyone always likes to start with an easy BLE example, and the go to example is the “Find Me”.  I started by building a Find Me Profile example, but when I looked my example I decided that I wanted to trace the actual application all the way back to the Bluetooth Special Interest Group (SIG) specification.  So, that is exactly what I do for this article.

  • Bluetooth SIG Find Me Profile
  • Bluetooth SIG Immediate Alert Service
  • Configure PSoC 6 BLE Schematic & Pins
  • Setup FreeRTOS and Retarget I/O
  • PSoC 6 BLE Firmware Architecture
  • Firmware for the alertTask
  • Firmware for the Immediate Alert Service Callback
  • Firmware for the BLE Callback
  • Firmware for the BLE Task
  • Firmware to start the system

Bluetooth SIG Find Me Profile

The Bluetooth SIG defines a bunch of standard profiles.  A standard Profile is just some combination of standardized Services and Characteristics.  One of those Profiles is the Find Me Profile.  The concept behind the Find Me Profile was that you could connect to a device with the Find Me Profile, and then send it an alert, at which point it would start “alerting” (presumably blinking or beeping).  You could imagine a tag attached to your car keys for instance.  You can get the Find Me Profile Specification from the Bluetooth SIG website.  The spec is a 10ish page pdf that says a Find Me Profile is just a device with an Immediate Alert Service server or client.  Here is a picture from the spec:

The other interesting part of the specification defines how the advertising is supposed to work

Immediate Alert Service

But, what is an Immediate Alert Service?  Well, you can get the Immediate Alert Service specification from the Bluetooth SIG website as well.  It basically says that there is one Service called Immediate Alert with a UUID of 0x1802 and that Service has one Characteristic called “Alert Level” with a UUID of 0x2A06. Here is a screen shot.

Unfortunately the spec doesnt have the UUIDs in it, and you have to follow the [1] to the website.  On the Bluetooth SIG GATT Services UUID definition webpage you can see the UUID of the Immediate Alert Service (0x1802)

And on the Bluetooth SIG GATT Characteristics definition webpage you can see the UUID of the Alert Level (0x2A06)

It also says the alert characteristic is writable with three values (No, Mild, High) and if you click on Alert Level you can see the definition of those:

Next you can see information about the Alert Characteristic.

Configure PSoC 6 BLE Schematic & Pins

Now, lets build the project.  Create a new PSoC 6 BLE project and edit the schematic to have a BLE, UART, and two digital output pins (red, led9).

Assign the pins to the correct place on the CY8CKIT-062-BLE development kit.  I am going to use led9 to show when the device is connected (or not)

Change the build settings to add FreeRTOS (Heap 4) and Retarget I/O

Configure the BLE to be dual core and a peripheral

Add the Find Me Target (GATT Server) profile

Once that is done you will see the Immediate Alert Service (with the correct UUID from above)

And the Alert Level with the correct UUID

Next give the device a name (“FindMe”)

The spec calls for the advertising setup to be as follows

But I have to admit that I dont like to wait… so I configured it like this:

Next, configure the advertising packet to have the name of the device and the fact that it has a IAS Service

Setup FreeRTOS and Retarget I/O

Run “Generate Application” to bring in all of the BLE and PDL firmware.  Then edit “stdio_user.h” to setup the stdio support.

#include "project.h"
/* Must remain uncommented to use this utility */
#define IO_STDOUT_ENABLE
#define IO_STDIN_ENABLE
#define IO_STDOUT_UART      UART_1_HW
#define IO_STDIN_UART       UART_1_HW

Next, modify FreeRTOSConfig.h to include support for semaphores.

#define configUSE_COUNTING_SEMAPHORES           1

and a much bigger stack

#define configTOTAL_HEAP_SIZE                   (48*1024)

And finally the interrupts to support BLE

/* Put KERNEL_INTERRUPT_PRIORITY in top __NVIC_PRIO_BITS bits of CM4 register */
#define configKERNEL_INTERRUPT_PRIORITY         0xFF
/* Put MAX_SYSCALL_INTERRUPT_PRIORITY in top __NVIC_PRIO_BITS bits of CM4 register */
//#define configMAX_SYSCALL_INTERRUPT_PRIORITY    0xBF
#ifdef __NVIC_PRIO_BITS
      /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
      #define configPRIO_BITS                   __NVIC_PRIO_BITS
#else
      #define configPRIO_BITS                   4        /* 15 priority levels */
#endif    

#define configMAX_SYSCALL_INTERRUPT_PRIORITY    ( 1 << (8 - configPRIO_BITS) )

Firmware Architecture

There will be two tasks

  1. The alertTask which will be responsible for setting the state of the red LED.  To set the state of the LED any other task can set the EventGroup bits.
  2. The bleTask which will handle running the BLE and the generic BLE callback and the IAS callback.

Firmware for the alertTask

The alert task will

  1. Set things up including eventBits which I call “alertState”
  2. Turn off the red led
  3. Go into an infinite loop
  4. The loop will either 1) wait until the end of time or 2) timeout at 500ms)
  5. Then it will determine the cause of the timeout and set the state of the red LED
// These BITs are used to set the state of the red LED
EventGroupHandle_t alertState;
#define ALERT_NO_MASK   1<<0
#define ALERT_MILD_MASK 1<<1
#define ALERT_HIGH_MASK 1<<2

/*****************************************************************************\
 * Function:    alertTask
 * Input:       FreeRTOS Template - unused argument
 * Returns:     void
 * Description: 
 *     This funtion is the mainloop for the alertTask.  It manages the state of
 *     the RED led.  Other tasks communitcate with this task using the alertState
 *     event bits.
\*****************************************************************************/
void alertTask(void *arg)
{
    (void)arg;
    printf("Alert Task Started\r\n");
    TickType_t delay=portMAX_DELAY;
    EventBits_t currentBits;
    
    alertState = xEventGroupCreate();
    xEventGroupSetBits(alertState,ALERT_NO_MASK);
   
    Cy_GPIO_Write(red_PORT,red_NUM,LED_OFF);
    
    while(1)
    {
        currentBits = xEventGroupWaitBits(alertState,ALERT_HIGH_MASK|ALERT_MILD_MASK|ALERT_NO_MASK,
            pdTRUE,pdFALSE,delay);
        switch(currentBits)
        {
            case ALERT_NO_MASK:
                delay = portMAX_DELAY;
                Cy_GPIO_Write(red_PORT,red_NUM,LED_OFF);
            break;
            case ALERT_HIGH_MASK:
                delay = portMAX_DELAY;
                Cy_GPIO_Write(red_PORT,red_NUM,LED_ON);
            break;
            case 0: // case 0 means timer expired & no bits set.   
            case ALERT_MILD_MASK:
                delay = 500;
                Cy_GPIO_Inv(red_PORT,red_NUM);
            break;
        }   
    }
}

Firmware for the Immediate Alert Service Callback

Cypress setup a bunch of APIs that know how to handle a bunch of the Bluetooth SIG Profiles/Services.  First lets look at the PSoC 6 BLE Middleware PDL Documentation to find the IAS Service.  It is in the “BLE Service-Specific API” section.

We want to implement a “GATT Server”… meaning our device has the IAS Server running on it so that a GATT Client can write into our database.  When I click on “IAS Server Functions” it takes me to this section of the documentation.  Basically what you do in your firmware is

  1. Register a callback with Cy_BLE_IAS_RegisterAttrCallback
  2. Setup the function that will be called back when the Alert Level characteristic is written.

Here is the documentation for the callback.  You can see that you need to make a function that matches the prototype of “cy_ble_callback_t”)

When you are called back you will get a void * which you can then cast into a pointer of type “cy_stc_ble_ias_char_value_t *”.  This structure will contain the value of the alert in the “cy_stc_ble_gatt_value_t *value” member.

If you look at the “cy_stc_ble_Gatt_value_t” structure you will find that it contains a pointer to an array of uint8_t (actually one one)

Now to actually write the callback function.  It just:

  1. Looks and sees if it is a write callback
  2. finds the value using the “Cy_BLE_IASS_GetCharacteristicValue” function.
  3. Then sends a message to the alertTask
/*****************************************************************************\
 * Function:    iasCallback
 * Input:       BLE IAS Service Handler Function: 
 *      - eventCode (which only can be CY_BLE_EVT_IASS_WRITE_CHAR_CMD
 *      - eventParam which is a pointer to  (and unused)
 * Returns:     void
 * Description: 
 *   This is called back by the BLE stack when there is a write to the IAS
 *   service.  This only occurs when the GATT Client Writes a new value
 *   for the alert.  The function figures out the state of the alert then
 *   sends a message to the alertTask usign the EventGroup alterState
\*****************************************************************************/
void iasCallback(uint32_t eventCode, void *eventParam)
{
    (void)eventParam;
    uint8_t alertLevel;
    
    if(eventCode == CY_BLE_EVT_IASS_WRITE_CHAR_CMD)
    {
        /* Read the updated Alert Level value from the GATT database */
        Cy_BLE_IASS_GetCharacteristicValue(CY_BLE_IAS_ALERT_LEVEL, 
            sizeof(alertLevel), &alertLevel);
        
        // The value of the characteristic could also be gotten like this:
        //switch(((cy_stc_ble_ias_char_value_t *)eventParam)->value->val[0])
        
        switch(alertLevel)
        {
            case CY_BLE_NO_ALERT:
                printf("No alert\n");
                xEventGroupSetBits(alertState,ALERT_NO_MASK);               
            break;
            case CY_BLE_MILD_ALERT:
                printf("Medium alert\n");
                xEventGroupSetBits(alertState,ALERT_MILD_MASK);               
            break;
            case CY_BLE_HIGH_ALERT:        
                printf("High alert\n");
                xEventGroupSetBits(alertState,ALERT_HIGH_MASK);               
            break;
        }   
    }   
}

Instead of calling the function to get the values, you could have also been done this:

switch(((cy_stc_ble_ias_char_value_t *)eventParam)->value->val[0])

Firmware for the Ble Event Handler

The BLE event handler is really simple,  it just prints out some debugging information depending on the event.  It also starts the advertising when the stack starts or when it has been disconnected.

/*****************************************************************************\
 * Function:    customEventHandler
 * Input:       Cy_BLE Event Handler event and eventParameter
 * Returns:     void
 * Description: 
 *   This funtion is the BLE Event Handler function.  It is called by the BLE
 *   stack when an event occurs 
\*****************************************************************************/
void customEventHandler(uint32_t event, void *eventParameter)
{
    (void)eventParameter; // not used
    switch (event)
    {
        case CY_BLE_EVT_STACK_ON:
            printf("Stack Started\r\n");
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX);
        break;

        case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
            printf("Disconnected\r\n");
            Cy_GPIO_Write(led9_PORT,led9_NUM,LED_OFF); // Turn the LED9 Off
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX);
        break;

        case CY_BLE_EVT_GATT_CONNECT_IND:
            printf("Connected\r\n");
            Cy_GPIO_Write(led9_PORT,led9_NUM,LED_ON); // Turn the LED9 On             
        break;
                
        default:
        break;
    }
}

Firmware for the bleTask

The bleTask has two functions

  1. an ISR that is called whenever an IPC event happens (so that it can unlock the semaphore to process events)
  2. a main function which starts the system, registers the callback and runs process events at the right time.
/*****************************************************************************\
 * Function:    bleInterruptNotify
 * Input:       void (it is called inside of the ISR)
 * Returns:     void
 * Description: 
 *   This is called back in the BLE ISR when an event has occured and needs to
 *   be processed.  It will then set/give the sempahore to tell the BLE task to
 *   process events.
\*****************************************************************************/
void bleInterruptNotify()
{
    BaseType_t xHigherPriorityTaskWoken;
    xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(bleSemaphore, &xHigherPriorityTaskWoken); 
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

/*****************************************************************************\
 * Function:    bleTask
 * Input:       A FreeRTOS Task - void * that is unused
 * Returns:     void
 * Description: 
 *  This task starts the BLE stack... and processes events when the bleSempahore
 *  is set by the ISR.
\*****************************************************************************/

void bleTask(void *arg)
{
    (void)arg;
    
    printf("BLE Task Started\n");

    bleSemaphore = xSemaphoreCreateCounting(UINT_MAX,0);
    
    Cy_BLE_Start(customEventHandler);
    Cy_BLE_IPC_RegisterAppHostCallback(bleInterruptNotify);
    
    while(Cy_BLE_GetState() != CY_BLE_STATE_ON) // Get the stack going
    {
        Cy_BLE_ProcessEvents();
    }
    
    Cy_BLE_IAS_RegisterAttrCallback (iasCallback);
    for(;;)
    {
        xSemaphoreTake(bleSemaphore,portMAX_DELAY);
        Cy_BLE_ProcessEvents();
    }
}

Main Firmware

Finally the main firmware just starts all of the tasks.

// Starts the system
int main(void)
{
    __enable_irq(); 
    
    UART_1_Start();
    setvbuf( stdin, NULL, _IONBF, 0 ); 
    setvbuf( stdout, NULL, _IONBF, 0 ); 
    printf("System Started\r\n");

    xTaskCreate(bleTask,"bleTask",8*1024,0,2,&bleTaskHandle);
    xTaskCreate(alertTask,"AlertTask",configMINIMAL_STACK_SIZE,0,1,0);
    
    vTaskStartScheduler();
    for(;;)
    {
    }
}

Test

After doing all of this, you can run CySmart to test the system:

 

The Lost Art of Assembly Language Programming

Cypress introduced it’s first mass market microcontroller in 2001. It used a Cypress designed 8 bit CISC processor running at 24 MHz, with as little as 4 KB Flash and 256 bytes RAM. Wrapped around that was a neat array of programmable analog and digital blocks. This may not sound like much, but with a creative mindset you could get these parts to do amazing things. For instance, I once implemented a complete ultrasonic ranging sensor with full wave analog demodulation in a single PSOC1 as shown below.

PSOC1 Ultrasonic Ranging

With CPU resources at a premium, you had to write tight, efficient code to get the most out of PSOC1. A single C library could consume the entire Flash. Consequently, I wrote a lot of assembly code. That’s not so bad, since I actually enjoy it more than C. There’s a certain elegance to well written, fully commented machine code. In the case of PSOC1, here’s what you had to work with: 5 registers, some RAM and Flash. That’s it. Real Men Write In Assembly.

M8C Architecture

 

We’ll start with simple machine code instruction to make the CPU do something. You can reference the M8C assembly language user guide here for more details. To get the M8C to execute 2+3=5 we write:

mov A,2       ;Load A with 2
add A,3       ;Add 3 to A. Result=5 is in A

We can get fancy by using variables. Let’s add R=P+Q. Assume P is at RAM location 0x20 and Q is at location 0x21, and R is at 0x22

;Initialize variables
mov [0x20],2  ;Load P with 2
mov [0x21],3  ;Load Q with 3

;Add variables
mov X,[0x20]  ;X <- P
mov A,[0x21]  ;A <- Q
adc [X],A     ;X <- P + Q
mov [0x22],X  ;R <- X

The fun thing about assembly is you can always dream up cool ways of doing things in less operations based on the machine’s instruction set. For example, we can simplify the above code as follows:

;Add variables
mov [0x20],[0x22]   ;R <- P
adc [0x22],[0x21]   ;R <- P + Q

In my experience, a good programmer with expert knowledge of the instruction set and CPU resources can always write better code than a compiler. There’s a certain human creativity that algorithms can’t match.

All that being said, I had not seen a good “machine code 101” tutorial for writing assembly in PSOC Creator on modern ARM M0 processors. So let’s walk through one now. We’ll use a CY8CKIT-145 and blink the LED. It’s just what happens to be laying around on the lab bench. Any PSOC4 kit will do.

CY8CKIT-145-40XX

We’ll start by creating a standard project in PSOC Creator, drop a Digital Output pin on the schematic and call it “LED”

Then open the .CYDWR file and drag pin LED to P2[5], since that’s where it is on the LED board. Yours may be in a different place on whatever board you are using.

Now under “Source Files” in the workspace directory you will delete main.c and replace with main.s

Now right clock on “Source Files”, select “Add New File” and select “GNU ARM Assembly File” in the dialog. Rename the file from GNUArmAssembly01.s to main.s

Your workspace ends up looking like this:

So far, so good. Now open main.s, delete everything if it’s not empty and add the following code. This sets up the IDE for M0 assembly architecture

// ==============================================
// ARM M0 Assembly Tutorial
//
// 01 – Blink LED
// ==============================================
.syntax unified
.text
.thumb

Next we need to include register definitions for the chip we are using. These are all from the PSOC4 Technical Reference Manual (TRM)

// ==============================================
// Includes
// ==============================================
.include “cydevicegnu_trm.inc”

Then we are going to do some .equ statements, same as #define in C. This identifies the Port 2 GPIO data register plus bits for the LED pin in on and off state

// ==============================================
// Defines
// ==============================================
.equ LED_DR,CYREG_GPIO_PRT2_DR          // LED data reg address
.equ LED_PIN,5                          // P2.5
.equ LED_OFF,1<<led_pin                 // 0010 0000
.equ LED_ON,~LED_OFF                    // 1101 1111

Now you add the right syntax to set up main()

// ==============================================
// main
// ==============================================
.global main
.func main, main
.type main, %function
.thumb_func

Finally we add the code for main, which is pretty simple:

main:
ldr r5,=LED_DR      // Load GPIO port addr to r5

loop0:
ldr r6,=LED_ON      // Move led data to r6
str r6,[r5]         // Write r6 data to r5 addr

ldr r0,=0xFFFFFF    // Argument passed in r0
bl CyDelayCycles    // Delay for N cycles

ldr r6,=LED_OFF     // Move led data to r6
str r6,[r5]         // Write r6 data to r5 addr

ldr r0,=0xFFFFFF    // Argument passed in r0
bl CyDelayCycles    // Delay for N cycles

b loop0             // Branch loop0

.endfunc            // End of main
.end                // End of code

One thing to note: The function CyDelayCycles is defined CyBootAsmGnu.s. Any function in assembly gets its arguments passed by the first 4 registers r0,r1,r2 and r3. Before calling the function you simply load r0 with the argument then do a bl (branch with link). This is also why I avoided the first 4 registers when messing with LED data. If you’re interested in doing more with ARM assembly, definitely read the Cortex M0+ Technical Reference Manual. It’s a great primer for the M0+ instruction set.

That’s it. End result is a blinking LED. Cool thing is you can use PSOC Creator with all it’s nice features, but sill access the power of machine code.

You can get the project ZIP file here.

Regards
Darrin Vallis

Bosch BMI160 w/PSoC 6 CY8CKIT-028-EPD

Summary

I have been working on a bunch of PSoC 6 projects in preparation for some new videos and for use at Embedded World.  For one of those project I need a motion sensitive remote control… and conveniently enough we put a Bosch BMI160 motion sensor onto the new CY8CKIT-028-EPD shield that comes with the CY8CKIT-062-BLE development kit.

In this article I will show you how to make a complete test system using PSoC 6 to talk to the BMI160.  The steps are:

  1. Clone the Bosch BMI160 Driver Library
  2. Create a new PSoC 6 project & add the driver library
  3. Create the HAL for the Bosch Driver
  4. Create the main firmware and test

Clone the Bosch BMI160 Driver Library

When I started this, I knew that the board had a motion sensor but I didnt know what kind.  I assumed that it was an I2C based sensor, so I attached the bridge control panel and probed the I2C bus.  But this is what it said:

Bridge Control Panel

So… what the hell?  Then I looked at the board to try to figure out what was going on… and low and behold… the board that I had was a prototype that was done before we added the motion sensor.  Here it is:

And here is a board with the sensor on it.

CY8CKIT-028-EPD with Bosch BMI160

When I plug in that board and test it with the Bridge Control Panel I get:

The next thing that I did was look at the schematics.  OK, you can see that the Inertial Measurement Unit (IMU) is a BMI160 that is connected to the I2C bus.  The other cool thing is that the devkit team hooked up the two interrupt lines.  These lines are typically used for the IMU to signal the PSoC 6 that something has happened (like maybe the user started moving).

After looking at the schematics, the next step was to look at the BMI160 datasheet and try to figure out how to interface with the device. Typically these devices have a boatload of registers with a mind boggling number of bit fields.  This is always the un-fun part of the process.  But this time when I went to the BMI160 device page on Bosch’s website, there was a button that says “Documents and Drivers” and when you click it, there is a link to GitHub with a BMI160 driver.  Score!

To make this work you just “git clone git@github.com:BoschSensortec/BMI160_driver.git”

Create New PSoC 6 project & with Bosch BMI160 driver library

So, lets get on with testing it.  First create a new PSoC 63 Project

Use a blank schematic

Give it a name

Add the Retarget I/O and FreeRTOS (from the build settings menu)

Add a UART and an I2C Master

To get the I2C to be a master you need to double click it and change it into a master

Then assign the pins

Run “Build -> Generate Application” to get all of the PDL firmware you need.

Edit stdio_user.h to use the UART (scan down the stdio_user.h to find the right place)

#include "project.h"
/* Must remain uncommented to use this utility */
#define IO_STDOUT_ENABLE
#define IO_STDIN_ENABLE
#define IO_STDOUT_UART      UART_1_HW
#define IO_STDIN_UART       UART_1_HW

Add the “BMI_driver” directory to the include path of the CM4 project.  (To get to this menu right click the project and pick “build settings”)

Add the Bosch Driver files to the project

 

Create the HAL for the Bosch driver

It is simple to use the Bosch driver.  All you need to do is update the HAL.

  1. Provide a function to write I2C registers
  2. Provide a function to read I2C registers
  3. Provide a function to delay for a specified number of milliseconds
  4. Create a structure to hold initialization information and function pointers

This device implements what Cypress calls the “EZI2C” protocol which is also known as an I2C EEPROM protocol.  The device is organized as an array of registers.  Each register has an address from 0->0xFF (a single byte of addresses).  To write to a register you need to

  1. Send an I2C Start
  2. Send the 7-bit I2C address
  3. Send a write bit (aka a 0)
  4. Send the register address you want to write to (dont confuse I2C address with the internal BMI160 address)
  5. Send the 8-bit value that you want to write
  6. Send a stop

A cool thing with EZI2C is that it keeps track of the address, and automatically increments the register address each time you write.  This means you can write a sequence of address without having to do a complete transaction for each address.

Given that introduction the write function is simple:

static int8_t BMI160BurstWrite(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
    
    Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
    Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
    for(int i = 0;i<len; i++)
    { 
        Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,data[i],0,&I2C_1_context);
    }
    
    Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
    
    return 0;
}

In order to read you do a similar transaction to write.  Specifically the steps are:

  1. Send an I2C Start
  2. Send the 7-bit I2c address
  3. Send a WRITE bit aka 0
  4. Send the address of the register you want to read
  5. Send an I2C re-start
  6. Read a byte
  7. Send a NAK
  8. Send a stop

The read transaction is similar to the write in that you can continue to read sequential bytes by sending an ACK.  The last byte you read should be NAK-ed to tell the remote device that you are done reading. Given that the code is also straight forward.

// This function supports the BMP180 library and read I2C Registers
static int8_t BMI160BurstRead(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
    
    Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
    Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
    Cy_SCB_I2C_MasterSendReStart(I2C_1_HW,dev_addr,CY_SCB_I2C_READ_XFER,0,&I2C_1_context);
    for(int i = 0;i<len-1; i++)
    {
        Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_ACK,&data[i],0,&I2C_1_context);
    }
    Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_NAK,&data[len-1],0,&I2C_1_context);
    
    Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
    
    
    return 0;
}

There is one error with both my read and write functions.  And that error is?  No error checking.  I have seen some intermittent weirdness in which the I2C bus gets locked that ends up requiring a reset to fix.  This could be prevented by checking error codes on the I2C functions.

Now that we have a read and write function we can setup our device:  To do this:

  1. Setup a structure of type bmi160_dev
  2. Initialize the function pointers
  3. Initialize the settings for the device
  4. Finally send the settings
static struct bmi160_dev bmi160Dev;

static void sensorsDeviceInit(void)
{

  int8_t rslt;
  vTaskDelay(500); // guess

  /* BMI160 */
  bmi160Dev.read = (bmi160_com_fptr_t)BMI160BurstRead;
  bmi160Dev.write = (bmi160_com_fptr_t)BMI160BurstWrite;
  bmi160Dev.delay_ms = (bmi160_delay_fptr_t)vTaskDelay;
  
  bmi160Dev.id = BMI160_I2C_ADDR;  // I2C device address

  rslt = bmi160_init(&bmi160Dev); // initialize the device
  if (rslt == 0)
    {
      printf("BMI160 I2C connection [OK].\n");
      bmi160Dev.gyro_cfg.odr = BMI160_GYRO_ODR_800HZ;
      bmi160Dev.gyro_cfg.range = BMI160_GYRO_RANGE_125_DPS;
      bmi160Dev.gyro_cfg.bw = BMI160_GYRO_BW_OSR4_MODE;

      /* Select the power mode of Gyroscope sensor */
      bmi160Dev.gyro_cfg.power = BMI160_GYRO_NORMAL_MODE;

      bmi160Dev.accel_cfg.odr = BMI160_ACCEL_ODR_1600HZ;
      bmi160Dev.accel_cfg.range = BMI160_ACCEL_RANGE_4G;
      bmi160Dev.accel_cfg.bw = BMI160_ACCEL_BW_OSR4_AVG1;
      bmi160Dev.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE;

      /* Set the sensor configuration */
      bmi160_set_sens_conf(&bmi160Dev);
      bmi160Dev.delay_ms(50);
    }
  else
    {
      printf("BMI160 I2C connection [FAIL].\n");
    }
}

Create the main firmware and test

Finally I test the firmware by running an infinite loop that prints out acceleration data.

void motionTask(void *arg)
{
    (void)arg;
    I2C_1_Start();
    sensorsDeviceInit();
    struct bmi160_sensor_data acc;

    while(1)
    {
        
        bmi160_get_sensor_data(BMI160_ACCEL_ONLY, &acc, NULL, &bmi160Dev);      
        printf("x=%4d y=%4d z=%4d\r\n",acc.x,acc.y,acc.z,);       
        vTaskDelay(200);
    }
}

Now you should have this:

And finally the whole program in one shot

#include "project.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include "bmi160.h"
static struct bmi160_dev bmi160Dev;
static int8_t BMI160BurstWrite(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
for(int i = 0;i<len; i++)
{ 
Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,data[i],0,&I2C_1_context);
}
Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
return 0;
}
// This function supports the BMP180 library and read I2C Registers
static int8_t BMI160BurstRead(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
Cy_SCB_I2C_MasterSendReStart(I2C_1_HW,dev_addr,CY_SCB_I2C_READ_XFER,0,&I2C_1_context);
for(int i = 0;i<len-1; i++)
{
Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_ACK,&data[i],0,&I2C_1_context);
}
Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_NAK,&data[len-1],0,&I2C_1_context);
Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
return 0;
}
static void sensorsDeviceInit(void)
{
int8_t rslt;
vTaskDelay(500); // guess
/* BMI160 */
bmi160Dev.read = (bmi160_com_fptr_t)BMI160BurstRead;
bmi160Dev.write = (bmi160_com_fptr_t)BMI160BurstWrite;
bmi160Dev.delay_ms = (bmi160_delay_fptr_t)vTaskDelay;
bmi160Dev.id = BMI160_I2C_ADDR;  // I2C device address
rslt = bmi160_init(&bmi160Dev); // initialize the device
if (rslt == 0)
{
printf("BMI160 I2C connection [OK].\n");
bmi160Dev.gyro_cfg.odr = BMI160_GYRO_ODR_800HZ;
bmi160Dev.gyro_cfg.range = BMI160_GYRO_RANGE_125_DPS;
bmi160Dev.gyro_cfg.bw = BMI160_GYRO_BW_OSR4_MODE;
/* Select the power mode of Gyroscope sensor */
bmi160Dev.gyro_cfg.power = BMI160_GYRO_NORMAL_MODE;
bmi160Dev.accel_cfg.odr = BMI160_ACCEL_ODR_1600HZ;
bmi160Dev.accel_cfg.range = BMI160_ACCEL_RANGE_4G;
bmi160Dev.accel_cfg.bw = BMI160_ACCEL_BW_OSR4_AVG1;
bmi160Dev.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE;
/* Set the sensor configuration */
bmi160_set_sens_conf(&bmi160Dev);
bmi160Dev.delay_ms(50);
}
else
{
printf("BMI160 I2C connection [FAIL].\n");
}
}
#define MAXACCEL 8000
void motionTask(void *arg)
{
(void)arg;
I2C_1_Start();
sensorsDeviceInit();
struct bmi160_sensor_data acc;
while(1)
{
bmi160_get_sensor_data(BMI160_ACCEL_ONLY, &acc, NULL, &bmi160Dev);
printf("x=%4d y=%4d z=%4d\r\n",acc.x,acc.y,acc.z);
vTaskDelay(200);
}
}
int main(void)
{
__enable_irq(); /* Enable global interrupts. */
UART_1_Start();
xTaskCreate( motionTask, "Motion Task",400,0,1,0);
vTaskStartScheduler();
while(1);
}
/* [] END OF FILE */