This post is part of my Roadtest Review of Cypress PSoC 62S2 Wi-Fi & BT5.0 Pioneer Dev Kit - Review. My review is splitted into multiple reviews and tutorials. Main page of review contains brief description of every chapter. Some projects are implemented in multiple variants. Main page of review contains brief description about every chapter, project and variants and contains some reccomendations for reading.

 

Table of Contents

 

Project 4 – CapSense and Bluetooth

In this project and tutorial, I will show how to make application that use Bluetooth Low Energy and capacitive slider. Both is possible to evaluate using roadtested board. Project will make application which enables user control RGB led over Bluetooth and control brightness of led using capacitive slider.

 

The project will be based on template CapSense Buttons and Slider (FreeRTOS) but I will modify template a lot. Create project and add retarget-io, rgb-led and Bluetooth stack libraries to project. Tutorial with pictures is part of project 1 – low level variant.

 

Bluetooth low energy

We will need two files which are part of Bluetooth demo projects from ModusToolbox. We will need app_platform_cfg.c and app_platform_cfg.h include to our project. These files contain declaration and definition of configuration structure for communication with BLE part of CYW43012 module. I attached these files at the end of article. You can download it from there.

 

We also need modify a FreeRTOSConfig.h for enabling running Bluetooth libraries and Capsense libraries in one project. Change configUSE_MUTEXES, configUSE_RECURSIVE_MUTEXES, configUSE_COUNTING_SEMAPHORES, configUSE_NEWLIB_REENTRANT, configSUPPORT_STATIC_ALLOCATION and configUSE_TRACE_FACILITY to 1. Increase also configTOTAL_HEAP_SIZE to 16384.

 

Then we need to setup compiler and linker to link Bluetooth libraries successfully. Open Makefile and find line beginning with COMPONENTS and change it to following line

 

COMPONENTS=FREERTOS WICED_BLE

 

Now open Bluetooth Configuration utility in Quick Launch panel. Switch to GAP Settings tab and change Device name to some name. In controlling application is hardwired “PSoC6 RoadTest Device”. If you change it something else, You will need to modify that app.

 

Switch to L2CAP Settings tab and check Enable L2CAP logical channels.

 

Switch to GATT Settings tab, right click the Server in tree, go Add Service and select Custom Service.

 

Using F2 key rename new service to RGB. Then Right click the service, select Add Characteristic and Custom Characteristic.

 

You need three characteristics so add additional one. Then add descriptor using context menu to each characteristic. Use Client Characteristic Configuration.

 

Using F2 key rename characteristics to RGB_R, RGB_G and RGB_B. Then click each characteristic and in right panel change his name, Format to 8bit and value to 0. Then also assign Read and Write permissions. Do it similarly for all 3 characteristics.

 

Then Save configuration and close window. This tool generated lot of code for us. We will use it later in our code.

 

Now go to main.c and delete all contents of that file. Add following includes. Most of them we will use later.

 

#include "cybsp.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "capsense_task.h"
#include "led_task.h"
#include "cycfg_gap.h"
#include "cycfg_gatt_db.h"
#include "cycfg_bt_settings.h"
#include "app_platform_cfg.h"
#include "wiced_bt_stack.h"
#include "cy_rgb_led.h"
#include "cycfg_capsense.h"
#include <stdio.h>

 

Add some variables that we will use later. uxTopPriorityUsed is variable which OpenOCD use to detect FreeRTOS.

 

volatile int uxTopUsedPriority;

uint8_t rgbR = 0;
uint8_t rgbG = 0;
uint8_t rgbB = 0;
uint8_t brightness = 100;

 

Initialize system, retarget-io library,rgb-led library and enable interrupts at the beginning of main. Then start FreeRTOS scheduler.

 

int main(void) {
    cy_rslt_t result;

    result = cybsp_init();
    CY_ASSERT(result == CY_RSLT_SUCCESS);

    result = cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, 115200);
    CY_ASSERT(result == CY_RSLT_SUCCESS);

    __enable_irq();

    result = cy_rgb_led_init(CYBSP_LED_RGB_RED, CYBSP_LED_RGB_GREEN, CYBSP_LED_RGB_BLUE, CY_RGB_LED_ACTIVE_LOW);
    CY_ASSERT(result == CY_RSLT_SUCCESS);

    vTaskStartScheduler();
    CY_ASSERT(0);
    return 1;
}

 

Now we will write function that initializes Bluetooth stack. Call it from main before starting scheduler.

 

void BLE_Init() {
}

 

In this function we need to initialize full stack using two functions. First is cybt_platform_config_init which initialize communication with module and second initializes stack on that module. Second function requires callback which library calls when some events (like connection, disconnection and so on) occurs.

 

wiced_result_t result = WICED_BT_SUCCESS;
cybt_platform_config_init(&bt_platform_cfg_settings);

result = wiced_bt_stack_init(BLE_ManagementCallback, &wiced_bt_cfg_settings);
CY_ASSERT(result == WICED_BT_SUCCESS);

 

Now let’s write BLE_ManagementCallback. We will check only for one event. When module became enabled, we will register our custom service, init database for GATT, enables paring and prepare data that we must broadcast to other devices for discovery and pairing. We will use structures that we have configured in utility. We also provide one callback for our service. This callback become called when request to our service will be received by module. Function looks as follows:

 

wiced_result_t BLE_ManagementCallback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t* eventData) {
    printf("BLE_ManagementCallback()\r\n");

    wiced_result_t result = WICED_BT_SUCCESS;

    if (event == BTM_ENABLED_EVT) {

        result = wiced_bt_gatt_register(BLE_GattCallback);
        CY_ASSERT(result == WICED_BT_SUCCESS);

        result = wiced_bt_gatt_db_init(gatt_database, gatt_database_len, NULL);
        CY_ASSERT(result == WICED_BT_SUCCESS);

        wiced_bt_set_pairable_mode(WICED_FALSE, WICED_FALSE);

        result = wiced_bt_ble_set_raw_advertisement_data(CY_BT_ADV_PACKET_DATA_SIZE, cy_bt_adv_packet_data);
        CY_ASSERT(result == WICED_BT_SUCCESS);

        result = wiced_bt_start_advertisements(BTM_BLE_ADVERT_UNDIRECTED_HIGH, 0, NULL);
        CY_ASSERT(result == WICED_BT_SUCCESS);
    }

    return WICED_BT_SUCCESS;
}

 

In BLE_GattCallback we need to handle requests to our RGB service. We will handle two types of requests – read and write. I will separate logic to BLE_GattRead and BLE_GattWrite function.

 

wiced_bt_gatt_status_t BLE_GattCallback(wiced_bt_gatt_evt_t event, wiced_bt_gatt_event_data_t* eventData) {
    printf("BLE_GattCallback()\r\n");

    if (event == GATT_ATTRIBUTE_REQUEST_EVT) {
        if (eventData->attribute_request.request_type == GATTS_REQ_TYPE_READ) {
            return BLE_GattRead(&eventData->attribute_request.data.read_req);
        } else if (eventData->attribute_request.request_type == GATTS_REQ_TYPE_WRITE) {
            return BLE_GattWrite(&eventData->attribute_request.data.write_req);
        }
    } else {
        return WICED_BT_GATT_ERROR;
    }
}

 

In BLE_GattRead I check which characteristic user requested and copy required value from my global variable to buffer prepared for user data.

 

wiced_bt_gatt_status_t BLE_GattRead(wiced_bt_gatt_read_t* req) {
    printf("BLE_GattRead()\r\n");

    if ((*req->p_val_len) < 1) {
        return WICED_BT_GATT_INVALID_ATTR_LEN;
    }

    if (req->handle == HDLC_RGB_RGB_R_VALUE) {
        req->p_val[0] = rgbR;
        *req->p_val_len = 1;
        return WICED_BT_GATT_SUCCESS;

    } else if (req->handle == HDLC_RGB_RGB_G_VALUE) {
        req->p_val[0] = rgbG;
        *req->p_val_len = 1;
        return WICED_BT_GATT_SUCCESS;

    } else if (req->handle == HDLC_RGB_RGB_B_VALUE) {
        req->p_val[0] = rgbB;
        *req->p_val_len = 1;
        return WICED_BT_GATT_SUCCESS;

    } else {
        return WICED_BT_GATT_READ_NOT_PERMIT;
    }

}

 

Similarly, in Write function I will write value from buffer to my global variable. I will also call LED_Update function which update color and brightness of RGB LED.

 

wiced_bt_gatt_status_t BLE_GattWrite(wiced_bt_gatt_write_t* req) {
 printf("BLE_GattWrite()\r\n");

    if (req->val_len != 1) {
 return WICED_BT_GATT_INVALID_ATTR_LEN;
    }

    if (req->handle == HDLC_RGB_RGB_R_VALUE) {
 rgbR = req->p_val[0];
 LED_Update();
 return WICED_BT_GATT_SUCCESS;

    } else if (req->handle == HDLC_RGB_RGB_G_VALUE) {
 rgbG = req->p_val[0];
 LED_Update();
 return WICED_BT_GATT_SUCCESS;

    } else if (req->handle == HDLC_RGB_RGB_B_VALUE) {
 rgbB = req->p_val[0];
 LED_Update();
 return WICED_BT_GATT_SUCCESS;

    } else {
 return WICED_BT_GATT_WRITE_NOT_PERMIT;
    }
}

 

LED_Update is easy function which calculates color value in format accepted by library from R, G and B values and then turn RGB led with that color on.

 

void LED_Update() {
 cy_rgb_led_on(cy_rgb_led_create_color(rgbR, rgbG, rgbB), brightness);
}

 

Now that’s all. I prepared Windows 10 application based on Universal Windows Platform (UWP) that you can run on Windows 10 Laptop with Bluetooth module. Description of that app is out of scope this tutorial, but application is available as Visual Studio Solution at the end of article. You can download and run it. Application looks as follows

 

When you click start watcher, application became listening for discovery messages from devices and then if any message received from device with specific name PSoC6 RoadTest Device, it connect to the device. If connection succeeds, application enables you to set value for R, G and B channels of RGB led. If you have Bluetooth capable device like mobile Phone, you can also check presence of Bluetooth device. You will see the name typed in configuration utility. But I have not created any mobile application to control RGB led using my proprietary protocol (that I designed using utilities at the begining).

 

Capsense

Next part of this project is CapSense. Program will update global variable “brightness” based on place where user placed his finger last time on capacitive slider. While slider has 5 segments, we can determine location of finger much more accurate because library detects and calculates location of finger from analog values. It can determine exact location based on ratio of analog values measured on siblings’ segments.

 

Create CAPSENSE_Init function.

 

void CAPSENSE_Init() {
}

 

At first, we initialize CapSense library and its context. Context is declared in one of files that was part of template that we have used for project creation. You can use Ctrl + Click to see it (if it does not work, try build project).

 

uint32_t status = CYRET_SUCCESS;
status = Cy_CapSense_Init(&cy_capsense_context);
CY_ASSERT(CYRET_SUCCESS == status);

 

Next, we need to configure interrupt from CapSense peripheral. In interrupt handler CAPSENSE_InterruptHandler we just call function of CapSense library that will do all required stuff for us.

 

const cy_stc_sysint_t CapSense_interrupt_config =
    {
        .intrSrc = csd_interrupt_IRQn,
 .intrPriority = 7,
    };
Cy_SysInt_Init(&CapSense_interrupt_config, CAPSENSE_InterruptHandler);
NVIC_ClearPendingIRQ(csd_interrupt_IRQn);
NVIC_EnableIRQ(csd_interrupt_IRQn);

 

Interrupt handler looks as follows:

 

void CAPSENSE_InterruptHandler(void) {
 Cy_CapSense_InterruptHandler(CYBSP_CSD_HW, &cy_capsense_context);
}

 

Now, we can enable CapSense peripheral in CAPSENSE_Init function.

 

status = Cy_CapSense_Enable(&cy_capsense_context);
CY_ASSERT(CYRET_SUCCESS == status);

 

Library for controlling CapSense works in way that we start scanning of widgets and when scan is completed, library calls our callback. Library process all required stuff in interrupt handler which we have registered. We need to register callback that will be called by library when all scans for all widgets are completed.

 

status = Cy_CapSense_RegisterCallback(CY_CAPSENSE_END_OF_SCAN_E, CAPSENSE_EndOfScanCallback, &cy_capsense_context);
CY_ASSERT(CYRET_SUCCESS == status);

 

And finally, we must start scanning of widgets.

 

status = Cy_CapSense_ScanAllWidgets(&cy_capsense_context);
CY_ASSERT(status == CY_RSLT_SUCCESS);

 

Now we must write CAPSENSE_EndOfScanCallback. In this callback we get information about status of slider widget and remap value to range 0 – 100 which is required by rgb-led library. Result we will store into brightness variable and in case if value changed, we call LED_Update similarly like we did in BLE section. At last, we start new scan of all widgets.

 

void CAPSENSE_EndOfScanCallback(cy_stc_active_scan_sns_t * ptrActiveScan) {
    Cy_CapSense_ProcessAllWidgets(&cy_capsense_context);

    cy_stc_capsense_touch_t* slider_touch_info = Cy_CapSense_GetTouchInfo(CY_CAPSENSE_LINEARSLIDER0_WDGT_ID, &cy_capsense_context);

    if (slider_touch_info->numPosition != 0) {
        uint16_t resolution = cy_capsense_context.ptrWdConfig[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].xResolution;
        uint16_t value = slider_touch_info->ptrPosition->x;

        uint8_t newBrightness = (value * 100) / resolution;
        if (brightness != newBrightness) {
            brightness = newBrightness;
            LED_Update();
        }
    }

    Cy_CapSense_ScanAllWidgets(&cy_capsense_context);
}

 

That’s all. You can run application. When you connect over Bluetooth and set color using application, you can now regulate brightness of RGB LED using touching and sliding over slider.