EW21: Lesson 0: Introduction & Resources

Summary

Hello everyone.  This is lesson 0 of a series of 7 lessons about creating an IoT application using the Infineon ModusToolbox Software Environment to create a WiFi enabled drone.  In the next two hours we will build a remote control that uses the PSoC 6 MCU, WiFi, MQTT, CapSense and a 3-D Magnetic Joystick.  Then we will build the drone which will use a PSoC 6, WiFi, MQTT, CapSense and a BLDC motor controller.

What I will do today is take you lesson by lesson through the class and talk about how it all works and what you need to do.  When I built the class it was absolutely my goal to have every button click and line of code described.  That being said,  it is likely that I made some errors.  So, during the class you will be able to send messages to my team who will answer the questions, or ask me and I’ll answer live.  If you missed the class, that’s OK, you will be able to watch it on replay.  In addition if you have a question after the live stream is over, leave a comment here and I’ll answer.

I will attempt to go slowly enough for you to follow along, but if I go to fast, don’t worry you should be able to follow along with the instructions on this website.

Every lesson will have this table in it and you will be able to click to follow along with the different lessons.

Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

 

Here is the overall system architecture:

The Remote Control

The remote control is built with two Infineon development boards

Kit Features
CY8CPROTO-062-4343W PSoC 6,CYW4343W WiFi Bluetooth Combo Radio,CapSense
TLE493D-W2B6 XENSIV 3-D Magnetic Sensor

Here are some pictures.

CY8CPROTO-062-4343W.  The top left of the board is a KitProg programmer.  The middle third on the left is the 4343W and the PSoC 6.  The bottom right are the CapSense buttons.  Just to the right of the programmer is a SD Card holder and S512FL Quad SPI Flash.

This the the top of the TLE-493D-W2B6 3-D magnetic sensor board.  On the far right, the tiny 6 pin chip is the actual sensor.  The big hole to the left of the sensor is to mount a magnet.  The two chips on the left are used as a bridge to USB (if you are developing).  I attach to this device using the I2C interface pins.

Here is the back where you can see the SCL, SDA, Power and Ground labeled.  Unfortunately, they are in the wrong order to plug directly into the PSoC kit so Greg had to make a little wire switcher.

Here is the board with the really really cool 3-d printed joystick.  Simply two pieces of plastic with a magnet in the bottom.

This picture show how the magnetic sense board is mounted onto the kit

Here is the whole thing assembled.

The Crazy Drone

The drone was built with

Kit Features
CY8CKIT-062S2-43012 PSoC 6 + CYW43012 Low Power Bluetooth WiFI Combo
TLE9879WXA40 BLDC Control Shield

Here is a picture of the CY8CKIT-062S2-43012.  On the far right in the middle is the PSoC 6 and CYW43012.  In the lower right are the CapSense Buttons and Slider.  The KitProg IC is just below the top Arduino Header.

In order to drive the BLDC motor I use the TLE9879WXA40 Motor Shield.  This has everything needed to do Field Oriented Control of a 3-phase BLDC motor.  The Blue, Green and White wires are the 3-phases of the BLDC.  The Red and Black are simply +12V and Ground.  You interface from the PSoC to the BLDC shield via a SPI interface (attached to the Arduino pins).

The BLDC motor is mounted into a 3-d printed holder.  The mount is attached to hollow carbon fiber tubes that run on bearings that you see below.  The wires run down through the tubes.

At the bottom, the blue box just provides +12v to the drone and the PSoC board.

Here is a closer picture of the BLDC motor.

Here is a picture of the whole crazy thing running.  If you look in the background you can see a top secret new PSoC motor controller.  Is that an Easter egg?

And yes, it will cut your fingers off if you aren’t careful.

Resources

You will need a few things for this class:

ModusToolbox Software Environment

You can download Modus Toolbox from here

CY8CPROTO-062-4343W

You can read all about this development kit on the website or in the KitGuide

CY8CKIT-062S2-43012

You can read all about this development kit on the website or in the KitGuide

TLE9879QXA40 BLDC Motor Controller

You can read all about this development kit on the website or in the KitGuide

XENSIV TLE493D

You can read all about this development kit on the website or in the KitGuide

EW21: Lesson 1: FreeRTOS + CapSense

Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

 

Summary

In lesson 1 we will create our first project, which like every good embedded project, will start with the blinking LED.  Then I will show you how to add a CapSense slider and two buttons.  At the end of lesson one you will have the “CapSense Task” colored green.  Here is the architecture:

Learning Objectives

By the end of this lesson I would like you to know about:

  1. Creating and building your first ModusToolbox PSoC 6 MCU Project
  2. The organization of a ModusToolbox project
  3. An introduction to CapSense

Procedure

We will follow these steps:

  1. Update manifest.loc
  2. Create the new project
  3. A Tour of your project
  4. Test the basic project
  5. Add a CapSense task header file
  6. Add a CapSense task dot-c file
  7. Update main.c
  8. Test the CapSense

    1. Update the manifest.loc

    After you install ModusToolbox, there is a subdirectory in your home directory called “.modustoolbox”.  You will almost never need to look in this directory.  However, it contains two interesting things:

    1. A cache of the libraries (more on this later)
    2. A file called “manifest.loc” which will allow you to extend the functionality of Modus Toolbox.

    By adding a URL to the manifest.loc you can add in new

    1. Middleware
    2. Board Support Packages
    3. Example Projects

    To make the Embedded World easier, I have extended ModusToolbox to give you access to some example projects and libraries that I created.  To get access you need to add the line “https://github.com/iotexpert/mtb2-iotexpert-manifests/raw/master/iotexpert-super-manifest.xml” into the manifest.loc file.  You can do that with any editor.  Here is how it looks on my Mac.

    arh ~ $ cd .modustoolbox/
    arh .modustoolbox $ ls
    cache		manifest.loc	manifest.loc~
    arh .modustoolbox $ more manifest.loc
    https://github.com/iotexpert/mtb2-iotexpert-manifests/raw/master/iotexpert-super-manifest.xml
    

    2. Create the new project

    Now I start the new project creator from the start menu.  Our first project will be using the CY8CPROTO-062-4343W, pick it and press next.

    In the filter box type “IoT” which will filter to my template projects.  To start with we will use the “IoT Expert FreeRTOS Template” which is a basic project I created that gives you a blinking LED, a UART console and FreeRTOS.  Pick that and give it a sensible name.

    After a bit of processing you will have a new application, press close.

    For this class I am going to use Visual Studio Code.  ModusToolbox also has an Eclipse IDE which you can use, or you can use the command line, IAR, or MicroVision – it’s entirely up to you.  To tell Visual Studio code about my project I will run “make vscode” which will build a vscode project.  Then I run vscode with “code .”

    The make vscode will build all of the files you need including a workspace.  Click”Open Workspace”

    3. A Tour of your Project

    Start a file browser and look around inside your project.

    You can also see these in Visual Studio Code.

    4. Test the basic project

    Now click the debug icon along the left and click the play button to build the project, program it and launch the debugger.

    (You can use the drop-down menu to select “Program (KitProg3_MiniProg4)” and then click the play button if you just want to build and program without launching the debugger.)

    Notice on the console a bunch of stuff comes ripping out… then it programs your development board.

    Once that is done the debugger starts and runs until the start of main.  Press play to get things going.

    5. Add a CapSense task header file

    Now that we have a functioning blinking LED project, let’s add CapSense.  Remember from the architecture picture above that I am going to have a task just for CapSense.  Start by making the public header file called “capsense_task.h”

    In that file there will only be the definition of the task.

    6. Add a CapSense task dot-c file

    Now we will create capsense_task.c.  In that file, you will need some includes to bring in the Infineon drivers.  After the includes I will define several functions

    1. capsense_init: Starts up the CapSense block
    2. process_touch: Figures out what is being touched on the buttons and slider
    3. capsense_isr: Used by the CapSense middleware to process the CapSense interrupt
    4. capsense_end_of_scan_callback: Tells your application when the scanning is done
    5. capsense_register_deepsleep: This allows the CPU to know when it can go to Deep Sleep without interfering with CapSense

    Finally I will define a semaphore to use to block my task until the CapSense scanning is done.

    #include <stdio.h>
    
    #include "cybsp.h"
    #include "cyhal.h"
    #include "cycfg.h"
    #include "cycfg_capsense.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    #include "semphr.h"
    
    #include "capsense_task.h"
    
    static void capsense_init(void);
    static void process_touch(void);
    static void capsense_isr(void);
    static void capsense_end_of_scan_callback(cy_stc_active_scan_sns_t* active_scan_sns_ptr);
    static void capsense_register_deepsleep(void);
    static QueueHandle_t capsense_done; // Semaphore set in Capsense Callback End of Scan

    The capsense_task function is called by FreeRTOS when it starts up.  This function does what it says

    1. Sets up a sempahore
    2. Initializes the CapSense
    3. Starts scanning
    4. Waits for a scan to complete using a semaphore
    5. Process the widgets
    6. Looks at the touches
    7. Starts another scan
    8. Waits, then loops back
    /*******************************************************************************
    * Function Name: task_capsense
    ********************************************************************************
    * Summary:
    *  Task that initializes the CapSense block and processes the touch input.
    *
    * Parameters:
    *  void *param : Task parameter defined during task creation (unused)
    *
    *******************************************************************************/
    void capsense_task(void* param)
    {
        (void)param;
    
        capsense_done = xQueueCreateCountingSemaphore(1,0);
    
        capsense_init();
    
        Cy_CapSense_ScanAllWidgets(&cy_capsense_context);
    
        for(;;)
        {
            xSemaphoreTake(capsense_done,portMAX_DELAY);
    
            Cy_CapSense_ProcessAllWidgets(&cy_capsense_context);
            process_touch();
            Cy_CapSense_ScanAllWidgets(&cy_capsense_context);
            vTaskDelay(50); // ~20hz update
        }
    }
    

    To initialize the CapSense you need to

    1. Initialize the  CapSense block
    2. Setup the CapSense interrupt
    3. Register the DeepSleep function
    4. Ask to be called back when a scan finishes
    5. Enable the CapSense block
    /*******************************************************************************
    * Function Name: capsense_init
    ********************************************************************************
    * Summary:
    *  This function initializes the CSD HW block, and configures the CapSense
    *  interrupt.
    *******************************************************************************/
    static void capsense_init(void)
    {
        Cy_CapSense_Init(&cy_capsense_context);
        
        static const cy_stc_sysint_t capSense_intr_config =
        {
            .intrSrc = csd_interrupt_IRQn,
            .intrPriority = 7,
        };
    
        /* Initialize CapSense interrupt */
        Cy_SysInt_Init(&capSense_intr_config, &capsense_isr);
        NVIC_ClearPendingIRQ(capSense_intr_config.intrSrc);
        NVIC_EnableIRQ(capSense_intr_config.intrSrc);
    
        capsense_register_deepsleep();
        Cy_CapSense_RegisterCallback(CY_CAPSENSE_END_OF_SCAN_E,
                                                  capsense_end_of_scan_callback, &cy_capsense_context);
        
        Cy_CapSense_Enable(&cy_capsense_context);
    }
    

    Infineon provides the ISR which is part of the CapSense scanning engine

    /*******************************************************************************
    * Function Name: capsense_isr
    ********************************************************************************
    * Summary:
    *  Wrapper function for handling interrupts from CSD block.
    *
    *******************************************************************************/
    static void capsense_isr(void)
    {
        Cy_CapSense_InterruptHandler(CYBSP_CSD_HW, &cy_capsense_context);
    }

    When a scan is done we want to unlock the capsense_task by giving the semaphore.

    /*******************************************************************************
    * Function Name: capsense_end_of_scan_callback
    ********************************************************************************
    * Summary:
    *  CapSense end of scan callback function. This function sends a command to
    *  CapSense task to process scan.
    *
    * Parameters:
    *  cy_stc_active_scan_sns_t * active_scan_sns_ptr (unused)
    *
    *******************************************************************************/
    static void capsense_end_of_scan_callback(cy_stc_active_scan_sns_t* active_scan_sns_ptr)
    {
        BaseType_t xYieldRequired;
    
        (void)active_scan_sns_ptr;
        xYieldRequired = xSemaphoreGiveFromISR(capsense_done,&xYieldRequired);
    
        portYIELD_FROM_ISR(xYieldRequired);
    }

    The DeepSleep registration function looks like this:

    /*******************************************************************************
    * Function Name: capsense_register_deepsleep
    ********************************************************************************
    * Summary:
    *  Wrapper function for setting up the Deep Sleep callback for CapSense.
    *  This is necessary so that a transition from active to Deep Sleep does not
    *  occur during a CapSense scan.
    *
    *  See the "MTB CAT1 Peripheral driver library documentation > PDL API Reference > SysPM"
    *  link in the Quick Panel Documentation for information on setting up the SysPm callbacks
    *******************************************************************************/
    static void capsense_register_deepsleep(void)
    {
        static cy_stc_syspm_callback_params_t callback_params =
        {
            .base       = CYBSP_CSD_HW,
            .context    = &cy_capsense_context
        };
    
        static cy_stc_syspm_callback_t capsense_deep_sleep_cb =
        {
            Cy_CapSense_DeepSleepCallback,
            CY_SYSPM_DEEPSLEEP,
            0,
            &callback_params,
            NULL,
            NULL
        };
    
        Cy_SysPm_RegisterCallback(&capsense_deep_sleep_cb);
    }

    The last bit of magic is the processing of the touches.  We will

    1. Look at the buttons
    2. If they are pressed do a print out
    3. Look a the slider (which gives you a value between 0 and 300).
    4. If the slider is being pressed, print out the value
    5. Save the current state of the buttons and slider for next time around.
    /*******************************************************************************
    * Function Name: process_touch
    *******************************************************************************/
    static void process_touch(void)
    {
        /* Variables used to store previous touch information */
        static uint32_t button0_status_prev = 0;
        static uint32_t button1_status_prev = 0;
        static uint16_t slider_pos_prev = 0;
    
        uint32_t button0_status = 0;
        uint32_t button1_status = 0;
        uint16_t slider_pos = 0;
        uint8_t slider_touched = 0;
        cy_stc_capsense_touch_t *slider_touch;
    
    // Process the buttons
    
        button0_status = Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON0_WDGT_ID,&cy_capsense_context);
        button1_status = Cy_CapSense_IsSensorActive( CY_CAPSENSE_BUTTON1_WDGT_ID, CY_CAPSENSE_BUTTON1_SNS0_ID, &cy_capsense_context);
    
        if((0u != button0_status) && (0u == button0_status_prev))
        {
            printf("Button 0 pressed\n");
        }
    
        if((0u != button1_status) && (0u == button1_status_prev))
        {
            printf("Button 1 pressed\n");
        }
    
    // Process the slider
        slider_touch = Cy_CapSense_GetTouchInfo( CY_CAPSENSE_LINEARSLIDER0_WDGT_ID, &cy_capsense_context);
        slider_pos = (slider_touch->ptrPosition->x / 3); // Transform 0-300 into 0-100
        slider_touched = slider_touch->numPosition;
    
        if((0u != slider_touched) && (slider_pos_prev != slider_pos ))
        {
            printf("Slider position %d\n",slider_pos);
        }
    
        button0_status_prev = button0_status;
        button1_status_prev = button1_status;
        slider_pos_prev = slider_pos;
    }

    7. Update main.c

    In main.c we need to include the capsense_task header file

    #include "capsense_task.h"
    

    And startup the capsense_task

    xTaskCreate(capsense_task, "CapSense", configMINIMAL_STACK_SIZE*4, NULL, 1, 0 );
    

    8. Test the CapSense

    Program your kit and you will be able to press the buttons and slider.

    Resources for Project

    Remember above when I started the project creator there was a list of projects.  This exact completed project is available to you as “IoT Expert Embedded World 2021 Lesson1”

    You can also clone this project from git@github.com:iotexpert/ew21-lesson1.git or https://github.com/iotexpert/ew21-lesson1.git

    For reference, here is the whole capsense_task.c file in one chunk
    #include <stdio.h>
    
    #include "cybsp.h"
    #include "cyhal.h"
    #include "cycfg.h"
    #include "cycfg_capsense.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    #include "semphr.h"
    
    #include "capsense_task.h"
    
    static void capsense_init(void);
    static void process_touch(void);
    static void capsense_isr(void);
    static void capsense_end_of_scan_callback(cy_stc_active_scan_sns_t* active_scan_sns_ptr);
    static void capsense_register_deepsleep(void);
    
    static QueueHandle_t capsense_done; // Semaphore set in Capsense Callback End of Scan
    
    /*******************************************************************************
    * Function Name: task_capsense
    ********************************************************************************
    * Summary:
    *  Task that initializes the CapSense block and processes the touch input.
    *
    * Parameters:
    *  void *param : Task parameter defined during task creation (unused)
    *
    *******************************************************************************/
    void capsense_task(void* param)
    {
        (void)param;
    
        capsense_done = xQueueCreateCountingSemaphore(1,0);
    
        capsense_init();
    
        Cy_CapSense_ScanAllWidgets(&cy_capsense_context);
    
        for(;;)
        {
            xSemaphoreTake(capsense_done,portMAX_DELAY);
    
            Cy_CapSense_ProcessAllWidgets(&cy_capsense_context);
            process_touch();
            Cy_CapSense_ScanAllWidgets(&cy_capsense_context);
            vTaskDelay(50); // ~20hz update
        }
    }
    
    /*******************************************************************************
    * Function Name: process_touch
    *******************************************************************************/
    static void process_touch(void)
    {
        /* Variables used to store previous touch information */
        static uint32_t button0_status_prev = 0;
        static uint32_t button1_status_prev = 0;
        static uint16_t slider_pos_prev = 0;
    
        uint32_t button0_status = 0;
        uint32_t button1_status = 0;
        uint16_t slider_pos = 0;
        uint8_t slider_touched = 0;
        cy_stc_capsense_touch_t *slider_touch;
    
    // Process the buttons
    
        button0_status = Cy_CapSense_IsWidgetActive(CY_CAPSENSE_BUTTON0_WDGT_ID,&cy_capsense_context);
        button1_status = Cy_CapSense_IsSensorActive( CY_CAPSENSE_BUTTON1_WDGT_ID, CY_CAPSENSE_BUTTON1_SNS0_ID, &cy_capsense_context);
    
        if((0u != button0_status) && (0u == button0_status_prev))
        {
            printf("Button 0 pressed\n");
        }
    
        if((0u != button1_status) && (0u == button1_status_prev))
        {
            printf("Button 1 pressed\n");
        }
    
    // Process the slider
        slider_touch = Cy_CapSense_GetTouchInfo( CY_CAPSENSE_LINEARSLIDER0_WDGT_ID, &cy_capsense_context);
        slider_pos = (slider_touch->ptrPosition->x / 3); // Transform 0-300 into 0-100
        slider_touched = slider_touch->numPosition;
    
        if((0u != slider_touched) && (slider_pos_prev != slider_pos ))
        {
            printf("Slider position %d\n",slider_pos);
        }
    
        button0_status_prev = button0_status;
        button1_status_prev = button1_status;
        slider_pos_prev = slider_pos;
    }
    
    
    /*******************************************************************************
    * Function Name: capsense_init
    ********************************************************************************
    * Summary:
    *  This function initializes the CSD HW block, and configures the CapSense
    *  interrupt.
    *******************************************************************************/
    static void capsense_init(void)
    {
        Cy_CapSense_Init(&cy_capsense_context);
        
        static const cy_stc_sysint_t capSense_intr_config =
        {
            .intrSrc = csd_interrupt_IRQn,
            .intrPriority = 7,
        };
    
        /* Initialize CapSense interrupt */
        Cy_SysInt_Init(&capSense_intr_config, &capsense_isr);
        NVIC_ClearPendingIRQ(capSense_intr_config.intrSrc);
        NVIC_EnableIRQ(capSense_intr_config.intrSrc);
    
        capsense_register_deepsleep();
    
        Cy_CapSense_RegisterCallback(CY_CAPSENSE_END_OF_SCAN_E,
                                                  capsense_end_of_scan_callback, &cy_capsense_context);
        
        Cy_CapSense_Enable(&cy_capsense_context);
    }
    
    /*******************************************************************************
    * Function Name: capsense_end_of_scan_callback
    ********************************************************************************
    * Summary:
    *  CapSense end of scan callback function. This function sends a command to
    *  CapSense task to process scan.
    *
    * Parameters:
    *  cy_stc_active_scan_sns_t * active_scan_sns_ptr (unused)
    *
    *******************************************************************************/
    static void capsense_end_of_scan_callback(cy_stc_active_scan_sns_t* active_scan_sns_ptr)
    {
        BaseType_t xYieldRequired;
    
        (void)active_scan_sns_ptr;
        xYieldRequired = xSemaphoreGiveFromISR(capsense_done,&xYieldRequired);
    
        portYIELD_FROM_ISR(xYieldRequired);
    }
    
    /*******************************************************************************
    * Function Name: capsense_isr
    ********************************************************************************
    * Summary:
    *  Wrapper function for handling interrupts from CSD block.
    *
    *******************************************************************************/
    static void capsense_isr(void)
    {
        Cy_CapSense_InterruptHandler(CYBSP_CSD_HW, &cy_capsense_context);
    }
    
    /*******************************************************************************
    * Function Name: capsense_register_deepsleep
    ********************************************************************************
    * Summary:
    *  Wrapper function for setting up the Deep Sleep callback for CapSense.
    *  This is necessary so that a transition from active to Deep Sleep does not
    *  occur during a CapSense scan.
    *
    *  See the "MTB CAT1 Peripheral driver library documentation > PDL API Reference > SysPM"
    *  link in the Quick Panel Documentation for information on setting up the SysPm callbacks
    *******************************************************************************/
    static void capsense_register_deepsleep(void)
    {
        static cy_stc_syspm_callback_params_t callback_params =
        {
            .base       = CYBSP_CSD_HW,
            .context    = &cy_capsense_context
        };
    
        static cy_stc_syspm_callback_t capsense_deep_sleep_cb =
        {
            Cy_CapSense_DeepSleepCallback,
            CY_SYSPM_DEEPSLEEP,
            0,
            &callback_params,
            NULL,
            NULL
        };
    
        Cy_SysPm_RegisterCallback(&capsense_deep_sleep_cb);
    }
    

    EW21: Lesson 2: Joystick

    Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

     

    Summary

    In lesson 2 we will add the Infineon XENSIV 3-D Magnetic Sensor with a Joystick to the project .  At the end of lesson two you will have the “JoyStick Task” colored green.  Here is the architecture:

    Learning Objectives

    In this lesson I will teach you about

    1. Libraries & the Library Manager
    2. Multiple RTOS Tasks
    3. XENSIV 3-D Magnetic JoyStick

    Procedure

    I am going to follow these steps:

    1. Look at the Sensor GUI
    2. Start a new project
    3. Add the TLx493D library
    4. Create the joystick_task public header
    5. Create the joystick_task implementation file
    6. Test

    1.Look at the Sensor GUI

    Start the GUI and click the connect button.

    Press start and you will now see x,y,z values.

    Click on the joystick view to see how it looks as a joystick.

    2. Start a new project

    Start the new project creator and pick out “CY8CPROTO-062-4343W”

    This time I will start from the previous project.  Press the “Import” button and select your old project to open.

    Give your project a new name (notice that we are starting from 1 as the template)

    Open up the command line and make vscode.

    Do a command line build/program by running “make -j program”.

    It will finish compiling and then program your board.

    Now look at the serial terminal and make sure that the CapSense is still working

    3. Add the TLx493D library

    In order to interact with the TLx493D it would be much simpler if you had a library.  Well, I have just the solution for that.  Start the library browser and filter for IoT.  Notice that one of your choices is the TLx493D 3D Magnetic Sensor library.  Check the box, then press Update. Once the update finishes, press Close.

    After running the update you will notice some new stuff.  First there is a file in your “deps” directory.  We will look at that in a second.  Also notice that there is a new directory in the mtb_shared directory.

    When you look at the dot-MTB file you will see that it is just a

    1. URL to GitHub
    2. A branch name = “master”
    3. A location to put it = mtb_shared/TLx493D_3Dmagnetic/master

    This is JUST A SIMPLE GIT REPOSITORY.

    When you open vscode and look in the repository you will see normal c-code.

    4. Create the Joystick_task public header

    We want to have a task called “joystick_task”.  So create the public header file for that task.

    Then add the long and extensive code.  Just a function prototype for the joystick task.

    #pragma once
    
    void joystick_task(void *param);
    

    5. Create the joystick_task implementation file

    Now the actual code for the joystick is straight forward

    1. Some includes
    2. The task definition
    3. Initialize the interface to the joystick with TLxI2CInit
    4. Initialize the library with TLx493xInit
    5. Loop repeatedly
    6. Read one frame from the joystick sensor
    7. Convert the frame to x-y coordinates
    8. If there is a new position print it.
    9. Save the old data
    10. Wait a bit
    11. Loop back to the start
    #include "cybsp.h"
    #include "FreeRTOS.h"
    #include "task.h"
    #include <stdio.h>
    
    #include "joystick_task.h"
    
    #include "PSoC_TLx_interface.h"
    #include "TLxJoystick.h"
    
    #define JOYSTICK_INTERVAL_MS    (100)   /* in milliseconds*/
    #define JOYSTICK_HYSTERESIS		(1)
    
    /*******************************************************************************
    * Function Name: task_joystick
    ********************************************************************************
    * Summary:
    *  Task that initializes the Joystick block and processes the input.
    *
    * Parameters:
    *  void *param : Task parameter defined during task creation (unused)
    *
    *******************************************************************************/
    void joystick_task(void* param)
    {
        (void)param;
    
        cy_rslt_t result;
    
        TLx493D_data_frame_t frame;
        TLxJoyStickXY_t joystick_curr;
        TLxJoyStickXY_t joystick_prev;
    
        /* Initialize I2C interface to talk to the TLx493D sensor */
        TLxI2CInit(CYBSP_I2C_SDA,
                            CYBSP_I2C_SCL,
                            0 /* Use Default Sensor Address */,
                            0 /* Use Default Speed */,
                            NULL /* Use Auto-assigned clock */);
    
    
        /* Configure the TLx493D sensor */
        result = TLx493D_init();
        if (result != CY_RSLT_SUCCESS)
        {
        	printf("Joystick not detected. Exiting Joystick task.\n");
        	vTaskDelete(NULL);
        }
        
        /* Set Sensor to Master Control Mode */
        TLx493D_set_operation_mode(TLx493D_OP_MODE_MCM);
    
        /* Repeatedly running part of the task */
        for(;;)
        {
            TLx493D_read_frame(&frame);
    
            TLxJoystickCovertXY(&frame,&joystick_curr);
    
            /* Only update/print new value if it has changed by more than the hysteresis value */
            if((joystick_curr.x > (joystick_prev.x + JOYSTICK_HYSTERESIS)) || (joystick_curr.x < (joystick_prev.x - JOYSTICK_HYSTERESIS)))
            {
                printf("Joystick Position: %d\n", joystick_curr.x);
    
                joystick_prev.x = joystick_curr.x;
                joystick_prev.y = joystick_curr.y;
            }
            vTaskDelay(JOYSTICK_INTERVAL_MS);
        }
    }
    

    6. Edit main.c

    In main.c you need to include the joystick_task.h and then start the task

    #include "cyhal.h"
    #include "cybsp.h"
    #include "cy_retarget_io.h"
    #include <stdio.h>
    #include "FreeRTOS.h"
    #include "task.h"
    #include "capsense_task.h"
    #include "joystick_task.h"
    
    volatile int uxTopUsedPriority ;
    
    void blink_task(void *arg)
    {
        cyhal_gpio_init(CYBSP_USER_LED,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,0);
    
        for(;;)
        {
        	cyhal_gpio_toggle(CYBSP_USER_LED);
        	vTaskDelay(500);
        }
    }
    
    
    int main(void)
    {
        uxTopUsedPriority = configMAX_PRIORITIES - 1 ; // enable OpenOCD Thread Debugging
    
        /* Initialize the device and board peripherals */
        cybsp_init() ;
        __enable_irq();
    
        cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);
    
        // Stack size in WORDs
        // Idle task = priority 0
        xTaskCreate(blink_task,    "blink"     ,configMINIMAL_STACK_SIZE*1  ,0 /* args */ ,0 /* priority */, 0 /* handle */);
        xTaskCreate(capsense_task, "CapSense"  ,configMINIMAL_STACK_SIZE*4  , NULL, 1, 0);
        xTaskCreate(joystick_task, "Joystick"  ,configMINIMAL_STACK_SIZE*4  , NULL, 1, 0);
    
        vTaskStartScheduler();
    }

    7. Test

    When you test, you will see that the joystick moves around and prints new values.  Plus the CapSense still works.

    Resources for Project

    You can find this completed project in your project creator dialog by filtering for “IoT Expert Embedded”.  This is lesson2

    You can also clone this project at git@github.com:iotexpert/ew21-lesson2.git or https://github.com/iotexpert/ew21-lesson2

    EW21: Lesson 3: WiFi

    Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

     

    Summary

    In lesson 3 we will add a task to manage the “Cloud”, this is IoT after all.  We will start by adding the connection to the WiFi network using the AnyCloud Wireless Connection Manager within ModusToolbox.  At the end of lesson three you will have the “Cloud Task” colored green – well actually partially green.  Here is the architecture:

    Learning Objectives

    1. AnyCloud Wireless Libraries
    2. Architecture of the PSoC 6 + 43xxxx WiFi Combo
    3. Wireless Connection Manager

    Procedure

    In this lesson we will follow these steps:

    1. Create a new project by copying lesson 2
    2. Add the WiFi Libraries
    3. Copy the wireless configuration templates files into your project
    4. Look at the Wireless Library Documentation
    5. Update the Makefile
    6. Add cloud_task.h
    7. Add cloud_task.c
    8. Update main.c
    9. Test

    1. Create a new project by copying lesson 2

    Start the new project creator and select the CY8CPROTO-062-4343W

    Click the import button.  Then select your previous project and press open.

    Select your new template and give it a reasonable name.

    Now program your project to make sure that things are still working.

    Here is the serial terminal.

    2. Add the WiFi Libraries

    Start up the library manager and go to the Libraries tab.  Click on wireless connection manager.  Notice that it will bring in a bunch of other libraries that it depends on.

    3. Copy the wireless configuration templates into your project

    MBED TLS and LWIP have about a billion options.  We provide a template for those options which you will need to copy into your project.  The templates are located in the wifi-mw-core library in the “config” directory.  Copy and paste them into your project.

    Here is a clip of the lwip configuration options.  I wouldn’t recommend changing anything unless you are sure you know what you are doing.

    4. Look at the Wireless Library Documentation

    All of the Infineon libraries will have a directory called “doc” and inside of that directory there will be a file called api_reference_manual.html

    When you open the file you will  see our doxygen generated documentation.  Here the WiFi connection manager documentation.

    5. Update the Makefile

    The makefile controls all of the libraries.  To enable these libraries you need to add the following “components” to the list if they aren’t already there. (Note that all of this information is in the Quick Start section of the library’s documentation).

    COMPONENTS= FREERTOS LWIP MBEDTLS

    And you need to tell the system about the configuration.

    DEFINES+= MBEDTLS_USER_CONFIG_FILE='"mbedtls_user_config.h"' 
    DEFINES+=CYBSP_WIFI_CAPABLE
    DEFINES+=CY_RTOS_AWARE

    6. Add the cloud_task.h

    Just like the other lessons you will need to create a public header file for the cloud task.

    Inside of that file there will be only one function prototype for the task.

    #pragma once
    
    void cloud_task(void *param);

    7. Add the cloud_task.c

    Make the cloud_task.c file to add the cloud infrastructure to your project.

    At the top of the file we will have some includes.  And I will define some macros that tell WiFi which AP to attach to. When you do this on your own, you will need to update those to match your WiFi AP SSID and password. The task itself is really simple for now, just connect to WiFi and then do nothing.

    #include <stdio.h>
    
    #include "FreeRTOS.h"
    #include "task.h"
    
    #include "cy_wcm.h"
    
    #include "cloud_task.h"
    
    #define CLOUD_WIFI_AP        "ew2021"
    #define CLOUD_WIFI_PW        "ew2021ap"
    #define CLOUD_WIFI_SECURITY  CY_WCM_SECURITY_WPA2_MIXED_PSK
    #define CLOUD_WIFI_BAND      CY_WCM_WIFI_BAND_ANY
    
    static void cloud_connectWifi();
    
    void cloud_task(void* param)
    {
        (void)param;
    
        cloud_connectWifi();
    
        while(1)
        {
            vTaskDelay(1);
    
        }
    }

    The connect to WiFi function will

    1. Setup the Access Point information
    2. Initialize the wireless connection manager
    3. Then go into a loop that will attempt to connect until there is success.
    4. Once a connection is made, it will print out the IP information.
    static void cloud_connectWifi()
    {
        cy_rslt_t result;
    
        cy_wcm_connect_params_t connect_param = {
            .ap_credentials.SSID = CLOUD_WIFI_AP,
            .ap_credentials.password = CLOUD_WIFI_PW,
            .ap_credentials.security = CLOUD_WIFI_SECURITY,
            .BSSID = {0},
            .band = CLOUD_WIFI_BAND,
        };
        cy_wcm_config_t config = {.interface = CY_WCM_INTERFACE_TYPE_STA}; // We are a station (not a Access Point)
    
        cy_wcm_init(&config); // Initialize the connection manager
    
        printf("\nWi-Fi Connection Manager initialized.\n");
    
        do
        {
            cy_wcm_ip_address_t ip_address;
    
            printf("Connecting to Wi-Fi AP '%s'\n", connect_param.ap_credentials.SSID);
            result = cy_wcm_connect_ap(&connect_param, &ip_address);
    
            if (result == CY_RSLT_SUCCESS)
            {
                printf("Successfully connected to Wi-Fi network '%s'.\n",
                        connect_param.ap_credentials.SSID);
    
                // Print IP Address
                if (ip_address.version == CY_WCM_IP_VER_V4)
                {
                    printf("IPv4 Address Assigned: %d.%d.%d.%d\n", (uint8_t)ip_address.ip.v4,
                            (uint8_t)(ip_address.ip.v4 >> 8), (uint8_t)(ip_address.ip.v4 >> 16),
                            (uint8_t)(ip_address.ip.v4 >> 24));
                }
                else if (ip_address.version == CY_WCM_IP_VER_V6)
                {
                    printf("IPv6 Address Assigned: %0X:%0X:%0X:%0X\n", (unsigned int)ip_address.ip.v6[0],
                            (unsigned int)ip_address.ip.v6[1], (unsigned int)ip_address.ip.v6[2],
                            (unsigned int)ip_address.ip.v6[3]);
                }
                break; /* Exit the for loop once the connection has been made */
            }
            else
            {
                printf("WiFi Connect Failed Retrying\n");
                vTaskDelay(2000); // wait 2 seconds and try again;
            }
    
        } while (result != CY_RSLT_SUCCESS);
    }
    

    8. Update main.c

    In main.c you need to include the cloud task

    #include "cloud_task.h"

    Then start the cloud task.

    xTaskCreate(cloud_task,    "Cloud"     ,configMINIMAL_STACK_SIZE*8  , NULL, 2, 0);  
    

    9. Test

    Program your project and make sure that it connects, and the CapSense and joystick still work.

    Resources for Project

    You can find this projects in your project creator dialog by filtering for “IoT Expert Embedded”.  This is lesson3

    You can also clone this project at git@github.com:iotexpert/ew21-lesson3.git or https://github.com/iotexpert/ew21-lesson3

    EW21: Lesson 4: MQTT

    Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

     

    Summary

    In lesson 4 we will finish the cloud task by adding a connection to the MQTT broker.  Then I will make a very minor update to the CapSense and Joystick tasks to send their data to the Cloud.  At the end of this lesson you will have the Cloud truly colored green.  Here is the architecture:

    Learning Objectives

    1. MQTT
    2. AnyCloud use of AWS libraries

    Procedure

    1. Create a new project by copying lesson 3
    2. Add the MQTT Library
    3. Copy the core MQTT configuration
    4. Update the Makefile
    5. Update the cloud_task.h
    6. Update the cloud_task.c
    7. Update joystick_task.c
    8. Update capsense_task.c
    9. Test (using an MQTT Client)
    10. Test (using our C++/Qt GUI)

    1. Create a new project by copying lesson 3

    Start the new project creator.  Pick the CY8CPROTO-062-4343W and press next.

    Click on the import button

    And select project three to start from.

    Give it a sensible name

    2. Add the MQTT Library

    Start the library browser and pick out the MQTT library.  Notice that it brings in a bunch of other libraries including the AWS libraries.

    3. Copy the core MQTT configuration

    When you look at the README for the MQTT it tells you to copy the file core_mqtt_config.h into your project.  Do so.

    4. Update the Makefile

    For this to work you need to make two changes to your Makefile.  First enable the secure sockets.

    COMPONENTS= FREERTOS LWIP MBEDTLS SECURE_SOCKETS

    Then ignore a few files from the AWS library.

    CY_IGNORE+=$(SEARCH_aws-iot-device-sdk-embedded-C)/libraries/standard/coreHTTP
    CY_IGNORE+=libs/aws-iot-device-sdk-embedded-C/libraries/standard/coreHTTP
    

    5. Update the cloud_task.h

    In order for other tasks to send a message I will create a function where they can send a “speed” also known as a percent 0-100.  This function will be called from both the CapSense task as well as the JoyStick task.

    void cloud_sendMotorSpeed(int speed);
    

    6. Update the cloud_task.c

    In the previous lesson we created the cloud task which connects to the WiFI network.  In this lesson we add MQTT.  Start by adding the includes for the MQTT and the RTOS queue.

    #include "cy_mqtt_api.h"
    #include "queue.h"

    Rather than have magic numbers (or values) hidden in my code I provide #defines for:

    1. The MQTT broker
    2. My Client ID
    3. The Topic to broadcast to
    4. The JSON key for the motor speed

    For other tasks to talk to this task they will push a value of the speed into a queue.  So I need to define that queue.  I will also use a static local variable to hold the handle of the MQTT connection.

    I then define functions to

    1. Connect to MQTT
    2. Start MQTT
    3. Publish to MQTT
    #define CLOUD_MQTT_BROKER        "mqtt.eclipseprojects.io"
    
    
    #define CLOUD_MQTT_CLIENT_PREFIX "remote"
    #define CLOUD_MQTT_TOPIC         "motor_speed"
    
    #define MOTOR_KEY                "motor"
    
    static QueueHandle_t motor_value_q;
    static cy_mqtt_t mqtthandle;
    
    static void cloud_connectWifi();
    static void cloud_startMQTT();
    static void cloud_mqtt_event_cb( cy_mqtt_t mqtt_handle, cy_mqtt_event_t event, void *user_data);
    
    static void cloud_publishMessage(char *topic,char *message);

    The updates to the cloud task are:

    1. Start WiFi
    2. Start MQTT
    3. Start the queue
    4. In the main loop, wait for a message, then publish it.
    void cloud_task(void* param)
    {
        (void)param;
    
        cloud_connectWifi();
        cloud_startMQTT();
    
        motor_value_q = xQueueCreate(1,sizeof(uint32_t));
    
        for(;;)
        {
            int motorSpeed;
            char message[32];
    
        	xQueueReceive(motor_value_q, &motorSpeed, portMAX_DELAY);
            snprintf(message, sizeof(message)-1, "{\"%s\":%d}",MOTOR_KEY,motorSpeed);
            cloud_publishMessage(CLOUD_MQTT_TOPIC,message);
        }
    }
    

    This function is used in other tasks to send the motor speed into the queue.

    void cloud_sendMotorSpeed(int speed)
    {
        if(motor_value_q)
            xQueueOverwrite(motor_value_q,&speed);
    }
    

    To start MQTT you need to

    1. Define the MQTT broker structure
    2. Create the MQTT system
    3. Define your connection information
    4. Create a random clientID (so there are no duplicated clients on the MQTT broker)
    5. Make the connection
    static void cloud_startMQTT()
    {
        static cy_mqtt_connect_info_t    	connect_info;
        static cy_mqtt_broker_info_t     	broker_info;
        static uint8_t buffer[1024];
    
        cy_rslt_t result;
    
        result = cy_mqtt_init();
        broker_info.hostname = CLOUD_MQTT_BROKER;
        broker_info.hostname_len = strlen(broker_info.hostname);
        broker_info.port = 1883;
    
        result = cy_mqtt_create( buffer, sizeof(buffer),
                                  NULL, &broker_info,
                                  cloud_mqtt_event_cb, NULL,
                                  &mqtthandle );
    
        CY_ASSERT(result == CY_RSLT_SUCCESS);
    
        static char clientId[32];
        srand(xTaskGetTickCount());
        snprintf(clientId,sizeof(clientId),"%s%6d",CLOUD_MQTT_CLIENT_PREFIX,rand());
        memset( &connect_info, 0, sizeof( cy_mqtt_connect_info_t ) );
        connect_info.client_id      = clientId;
        connect_info.client_id_len  = strlen(connect_info.client_id);
        connect_info.keep_alive_sec = 60;
        connect_info.will_info      = 0;
        connect_info.clean_session = true;
    
    
        result = cy_mqtt_connect( mqtthandle, &connect_info );
        CY_ASSERT(result == CY_RSLT_SUCCESS);
        printf("MQTT Connect Success to %s Client=%s\n",CLOUD_MQTT_BROKER,clientId);
    
    }

    You need an MQTT callback.  This is unused in this lesson but we will modify it for the drone.  Just copy it into your project

    static void cloud_mqtt_event_cb( cy_mqtt_t mqtt_handle, cy_mqtt_event_t event, void *user_data )
    {
        cy_mqtt_publish_info_t *received_msg;
        printf( "\nMQTT App callback with handle : %p \n", mqtt_handle );
        (void)user_data;
        switch( event.type )
        {
            case CY_MQTT_EVENT_TYPE_DISCONNECT :
                if( event.data.reason == CY_MQTT_DISCONN_TYPE_BROKER_DOWN )
                {
                    printf( "\nCY_MQTT_DISCONN_TYPE_BROKER_DOWN .....\n" );
                }
                else
                {
                    printf( "\nCY_MQTT_DISCONN_REASON_NETWORK_DISCONNECTION .....\n" );
                }
                break;
            case CY_MQTT_EVENT_TYPE_PUBLISH_RECEIVE :
                received_msg = &(event.data.pub_msg.received_message);
                printf( "Incoming Publish Topic Name: %.*s\n", received_msg->topic_len, received_msg->topic );
                printf( "Incoming Publish message Packet Id is %u.\n", event.data.pub_msg.packet_id );
                printf( "Incoming Publish Message : %.*s.\n\n", ( int )received_msg->payload_len, ( const char * )received_msg->payload );
                break;
            default :
                printf( "\nUNKNOWN EVENT .....\n" );
                break;
        }
    }

    To publish a message you

    1. Create the message
    2. Send the publish
    static void cloud_publishMessage(char *topic,char *message)
    {
        cy_mqtt_publish_info_t  pub_msg;
            
        pub_msg.qos = CY_MQTT_QOS0;
        pub_msg.topic = topic;
        pub_msg.topic_len = strlen(pub_msg.topic);
        pub_msg.payload = message;
        pub_msg.payload_len = strlen(message);
        
        cy_mqtt_publish( mqtthandle, &pub_msg );
        printf("Published to Topic=%s Message=%s\n",topic,message);
        
    }
    

    7. Update joystick_task.c

    We want the joystick task to be able to send joystick positions.  Include the cloud_task.h so that it gets access to the function to send messages.

    #include "cloud_task.h"

    Update the joystick function to call the send message function.

    /* Only update/print new value if it has changed by more than the hysteresis value */
    if((joystick_curr.x > (joystick_prev.x + JOYSTICK_HYSTERESIS)) || (joystick_curr.x < (joystick_prev.x - JOYSTICK_HYSTERESIS)))
    {
        printf("Joystick Position: %d\n", joystick_curr.x);
        cloud_sendMotorSpeed(joystick_curr.x); 
    
    }

    8. Update capsense_task.c

    We want the CapSense task to be able to send CapSense positions.  Include the cloud_task.h so that it gets access to the function to send messages.

    #include "cloud_task.h"

    Update the button code to send either 0 or 75 (0=STOP!!!#*##&&#) (75=take off)

    if((0u != button0_status) && (0u == button0_status_prev))
    {
        printf("Button 0 pressed\n");
        cloud_sendMotorSpeed(0); // Stop the Motor
    }
    
    if((0u != button1_status) && (0u == button1_status_prev))
    {
        printf("Button 1 pressed\n");
        cloud_sendMotorSpeed(75); // Set the motor to 75%
    }

    The slider just sends the current position.

    if((0u != slider_touched) && (slider_pos_prev != slider_pos ))
    {
        printf("Slider position %d\n",slider_pos);
        cloud_sendMotorSpeed(slider_pos);
    }

    9. Test (using an MQTT Client)

    You can download/install MQTTBox from here  This is a Mac/Linux/Windows MQTT client.  This will enable you to attach to the MQTT broker and then subscribe to the motor speed topic.

    Setup the configuration for the MQTT broker by pressing the gear button

    Then program your remote control and send some joystick and CapSense values.

    And you will see them coming out on the MQTTBox client

    10. Test (using our C++/Qt GUI)

    My friend Butch created a C++/Qt GUI to “simulate” the drone.  You can find it in the “simulator” directory of project 4.  Here is what it looks like.  You will need to put in

    1. Which broker you are using
    2. The name of your Topic

    Resources for Project

    You can find this completed project in your project creator dialog by filtering for “IoT Expert Embedded”.  This is lesson 4

    You can also clone this project at git@github.com:iotexpert/ew21-lesson4.git or https://github.com/iotexpert/ew21-lesson4

    Here is the final cloud_task.c

    #include <stdio.h>
    
    #include "FreeRTOS.h"
    #include "task.h"
    #include "queue.h"
    
    #include "cy_wcm.h"
    #include "cy_mqtt_api.h"
    
    #include "cloud_task.h"
    
    
    #define CLOUD_WIFI_AP        "ew2021"
    #define CLOUD_WIFI_PW        "ew2021ap"
    #define CLOUD_WIFI_SECURITY  CY_WCM_SECURITY_WPA2_AES_PSK
    #define CLOUD_WIFI_BAND      CY_WCM_WIFI_BAND_ANY
    
    #define CLOUD_MQTT_BROKER        "mqtt.eclipseprojects.io"
    
    
    #define CLOUD_MQTT_CLIENT_PREFIX "remote"
    #define CLOUD_MQTT_TOPIC         "motor_speed"
    
    #define MOTOR_KEY                "motor"
    
    static QueueHandle_t motor_value_q;
    static cy_mqtt_t mqtthandle;
    
    static void cloud_connectWifi();
    static void cloud_startMQTT();
    static void cloud_mqtt_event_cb( cy_mqtt_t mqtt_handle, cy_mqtt_event_t event, void *user_data);
    
    static void cloud_publishMessage(char *topic,char *message);
    
    void cloud_task(void* param)
    {
        (void)param;
    
        cloud_connectWifi();
        cloud_startMQTT();
    
        motor_value_q = xQueueCreate(1,sizeof(uint32_t));
    
        for(;;)
        {
            int motorSpeed;
            char message[32];
    
        	xQueueReceive(motor_value_q, &motorSpeed, portMAX_DELAY);
            snprintf(message, sizeof(message)-1, "{\"%s\":%d}",MOTOR_KEY,motorSpeed);
            cloud_publishMessage(CLOUD_MQTT_TOPIC,message);
        }
    }
    
    void cloud_sendMotorSpeed(int speed)
    {
        if(motor_value_q)
            xQueueSend(motor_value_q,&speed,0);
    }
    
    static void cloud_connectWifi()
    {
        cy_rslt_t result;
    
        cy_wcm_connect_params_t connect_param = {
            .ap_credentials.SSID = CLOUD_WIFI_AP,
            .ap_credentials.password = CLOUD_WIFI_PW,
            .ap_credentials.security = CLOUD_WIFI_SECURITY,
        	.static_ip_settings = 0,
            .BSSID = {0},
            .band = CLOUD_WIFI_BAND,
        };
        cy_wcm_config_t config = {.interface = CY_WCM_INTERFACE_TYPE_STA}; // We are a station (not a Access Point)
    
        cy_wcm_init(&config); // Initialize the connection manager
    
        printf("\nWi-Fi Connection Manager initialized.\n");
    
        do
        {
            cy_wcm_ip_address_t ip_address;
    
            printf("Connecting to Wi-Fi AP '%s'\n", connect_param.ap_credentials.SSID);
            result = cy_wcm_connect_ap(&connect_param, &ip_address);
    
            if (result == CY_RSLT_SUCCESS)
            {
                printf("Successfully connected to Wi-Fi network '%s'.\n",
                        connect_param.ap_credentials.SSID);
    
                // Print IP Address
                if (ip_address.version == CY_WCM_IP_VER_V4)
                {
                    printf("IPv4 Address Assigned: %d.%d.%d.%d\n", (uint8_t)ip_address.ip.v4,
                            (uint8_t)(ip_address.ip.v4 >> 8), (uint8_t)(ip_address.ip.v4 >> 16),
                            (uint8_t)(ip_address.ip.v4 >> 24));
                }
                else if (ip_address.version == CY_WCM_IP_VER_V6)
                {
                    printf("IPv6 Address Assigned: %0X:%0X:%0X:%0X\n", (unsigned int)ip_address.ip.v6[0],
                            (unsigned int)ip_address.ip.v6[1], (unsigned int)ip_address.ip.v6[2],
                            (unsigned int)ip_address.ip.v6[3]);
                }
                break; /* Exit the for loop once the connection has been made */
            }
            else
            {
                printf("WiFi Connect Failed Retrying\n");
                vTaskDelay(2000); // wait 2 seconds and try again;
            }
    
        } while (result != CY_RSLT_SUCCESS);
    
    }
    
    static void cloud_startMQTT()
    {
        static cy_mqtt_connect_info_t    	connect_info;
        static cy_mqtt_broker_info_t     	broker_info;
        static uint8_t buffer[1024];
    
        cy_rslt_t result;
    
        result = cy_mqtt_init();
        broker_info.hostname = CLOUD_MQTT_BROKER;
        broker_info.hostname_len = strlen(broker_info.hostname);
        broker_info.port = 1883;
    
        result = cy_mqtt_create( buffer, sizeof(buffer),
                                  NULL, &broker_info,
                                  cloud_mqtt_event_cb, NULL,
                                  &mqtthandle );
    
        CY_ASSERT(result == CY_RSLT_SUCCESS);
    
        static char clientId[32];
        srand(xTaskGetTickCount());
        snprintf(clientId,sizeof(clientId),"%s%6d",CLOUD_MQTT_CLIENT_PREFIX,rand());
        memset( &connect_info, 0, sizeof( cy_mqtt_connect_info_t ) );
        connect_info.client_id      = clientId;
        connect_info.client_id_len  = strlen(connect_info.client_id);
        connect_info.keep_alive_sec = 60;
        connect_info.will_info      = 0;
        connect_info.clean_session = true;
    
    
        result = cy_mqtt_connect( mqtthandle, &connect_info );
        CY_ASSERT(result == CY_RSLT_SUCCESS);
        printf("MQTT Connect Success to %s Client=%s\n",CLOUD_MQTT_BROKER,clientId);
    
    }
    
    static void cloud_mqtt_event_cb( cy_mqtt_t mqtt_handle, cy_mqtt_event_t event, void *user_data )
    {
        cy_mqtt_publish_info_t *received_msg;
        printf( "\nMQTT App callback with handle : %p \n", mqtt_handle );
        (void)user_data;
        switch( event.type )
        {
            case CY_MQTT_EVENT_TYPE_DISCONNECT :
                if( event.data.reason == CY_MQTT_DISCONN_TYPE_BROKER_DOWN )
                {
                    printf( "\nCY_MQTT_DISCONN_TYPE_BROKER_DOWN .....\n" );
                }
                else
                {
                    printf( "\nCY_MQTT_DISCONN_REASON_NETWORK_DISCONNECTION .....\n" );
                }
                break;
            case CY_MQTT_EVENT_TYPE_PUBLISH_RECEIVE :
                received_msg = &(event.data.pub_msg.received_message);
                printf( "Incoming Publish Topic Name: %.*s\n", received_msg->topic_len, received_msg->topic );
                printf( "Incoming Publish message Packet Id is %u.\n", event.data.pub_msg.packet_id );
                printf( "Incoming Publish Message : %.*s.\n\n", ( int )received_msg->payload_len, ( const char * )received_msg->payload );
                break;
            default :
                printf( "\nUNKNOWN EVENT .....\n" );
                break;
        }
    }
    
    static void cloud_publishMessage(char *topic,char *message)
    {
        cy_mqtt_publish_info_t  pub_msg;
            
        pub_msg.qos = CY_MQTT_QOS0;
        pub_msg.topic = topic;
        pub_msg.topic_len = strlen(pub_msg.topic);
        pub_msg.payload = message;
        pub_msg.payload_len = strlen(message);
        
        cy_mqtt_publish( mqtthandle, &pub_msg );
        printf("Published to Topic=%s Message=%s\n",topic,message);
        
    }
    

    EW21: Lesson 5: Low Power

    Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

     

    Summary

    The FreeRTOS template we started with already enables low power mode transitions such as Sleep and Deep Sleep. In lesson 5 we will update our remote control project to have even lower power as remote controls are almost always driven by batteries.  We will do this by using lower power regulators, slowing down clocks, and by offloading some functionality to the WiFi chip. This will be the final lesson on the remote control.  Here is the architecture:

    Learning Objectives

    This lesson will show you two important features of Modus Toolbox.

    1. How to reduce system power using the Low Power Assistant
    2. How to create a custom board support package

    Procedure

    1. Create your project
    2. Create a custom Board Support Package
    3. Modify the Makefile to use the new BSP
    4. Add the Low Power Assistant to the project
    5. Use the Configurator to setup Low Power
    6. Examine FreeRTOSConfig.h
    7. Test the project

    1. Create the project

    Start the project creator and make a copy of your 4th project.

    Call it Lesson_5

    2. Override the BSP Configuration

    Make a directory called “COMPONENT_CUSTOM_DESIGN_MODUS” to contain the configuration files that will override the configuration from the BSP.  This will be brought into the project automatically by the build system.

    Note: You can also create a complete custom BSP which allows changing things like linker scripts, startup code, etc. See the ModusToolbox User Guide for details on how to do that.

    Make a directory called “TARGET_CY8CPROTO-062-4343W” in your project.  This directory will hold the custom BSP files.

    Copy and paste the configuration files from the Modus Toolbox BSP directly into your project.

    Now your project should have these files:

    3. Modify the Makefile

    You need to

    1. Enable your new BSP
    2. Disable the existing BSP

    Do this by adding to the COMPONENTS and DISABLE_COMPONENTS line in the Makefile

    COMPONENTS= FREERTOS LWIP MBEDTLS SECURE_SOCKETS CUSTOM_DESIGN_MODUS WCM
    DISABLE_COMPONENTS=BSP_DESIGN_MODUS

    4. Add the Low Power Assistant to the project

    Start the Library Manager and add the LPA (Low Power Assistant) library to your project

    5. Use the configurator to Setup for Low Power

    Set the System Active Power Mode to ULP and enable the “Normal Current Buck”.

    Notice that the System Idle Power Mode is already set to System Deep Sleep. This means that when the RTOS is not busy, it will attempt to enter Deep Sleep mode.

    Turn down the FLL to 50 Mhz

    Lower the frequency of the peripherals by cutting the divider in half.

    In order to save power the host can sleep and wait for WiFi packets.  The WiFi chip needs to be able to wakeup the host PSoC.  Enable P0[4] as the wakeup pin and turn on the interrupt.

     

    Click on the CYW4343WKUGB tab.  Enable the host wakeup option.  Set the pin to P0[4].  Enable ARP off loads.  You can also look through the other low power offload filters.

    6. Examine FreeRTOSConfig.h

    Along with the System Idle Power Mode in the Configurator, this block of code tells FreeRTOS that when the system is idle that it should go to sleep.

    The function vApplicationSleep is in the abstraction-rtos library and it allows the system to go to sleep and wake up cleanly in the context of FreeRTOS.

    #if CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_SLEEP || CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_DEEPSLEEP
    extern void vApplicationSleep( uint32_t xExpectedIdleTime );
    #define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )
    #define configUSE_TICKLESS_IDLE  2
    #endif
    
    #if CY_CFG_PWR_DEEPSLEEP_LATENCY>0
    #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP CY_CFG_PWR_DEEPSLEEP_LATENCY
    #endif
    

    7. Test

    Program and test to make sure that things still work.  If you have a DMM plug it in and find out how much power you saved.

    Resources for Project

    You can find the completed project in your project creator dialog by filtering for “IoT Expert Embedded”.  This is lesson5

    You can also clone this project at git@github.com:iotexpert/ew21-lesson5.git or https://github.com/iotexpert/ew21-lesson5

    EW21: Lesson 6: Motor Control

    Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

     

    Summary

    In lesson 6 we will turn our attention to the drone. Everything on the drone side is almost exactly the same as remote control, so I will copy all of that code to start with then add the motor controller.  At the end of lesson one you will have the functioning drone controller.  Here is the architecture:

    Learning Objectives

    1. Retargeting a project to a different board
    2. Using the Infineon BLDC Motor Controller
    3. Subscribing to a MQTT topic

    Procedure

    1. Make a new project
    2. Rip out the joystick_task.c/h and fix main.c
    3. Add the motor driver library
    4. Create motor_task.h
    5. Create motor_task.c
    6. Update the CapSense task to submit to the motor_task
    7. Fix the Cloud Task to perform MQTT subscribe instead of publish
    8. Fix main.c to start the motor task
    9. Test

    1. Make a new project

    In order to control the BLDC motor I am going to a development kit that has Arduino headers, the CY8CKIT-062S2-43012.  Start your project from that BSP.

    Our project is basically the same as Lesson4, so import that as a template.

    Don’t sully your project’s name, pick something good.

    2. Rip out the joystick_task

    This project will not have the joystick task.  Nuke it! Don’t forget to remove the task start from main.c

    3. Add the Motor Driver Library and Remove the Magnetic Sensor

    For this project we will be talking to the TLE9879 BLDC motor shield.  I provide you a driver library for that.  And we won’t need the 3-D Magnetic sensing library any more.

    4. Create motor_task.h

    Create a motor_task.h  This will have the definition of the task, plus a function to set the motor speed by submitting to a queue.

    #pragma once
    
    void motor_task(void* param);
    
    void motor_update(int speed);
    

    5. Create motor_task.c

    The motor task needs some include files.  This task will also use a queue to manage other tasks (cloud and CapSense) that will submitting data to change the speed of the motor.

    #include "cybsp.h"
    #include "cyhal.h"
    #include "cycfg.h"
    #include "FreeRTOS.h"
    #include "task.h"
    #include "queue.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    
    #include "motor_task.h"
    
    #include "tle9879_system.h"
    
    static QueueHandle_t motor_value_q;

    I will define a few constants that will help me map 0-100% into 0-5500 RPM (the max for this motor). The motor needs to run at a decent speed to start up properly so we will map 10% to 1000 RPM. Below 10%, the motor will be off.

    /*******************************************************************************
    * Global constants
    *******************************************************************************/
    #define		RPM_CHANGE_INTERVAL		(100)
    #define		RPM_CHANGE_RATE			(10)
    #define		RPM_PERCENT_MIN			(10)
    
    #define  	RPM_MAX 				5500.0
    #define  	RPM_MIN 				1000.0
    

    The task is pretty simple, but with one trick.  Apparently you ramp changes in motor speed, so I use a timeout in the message queue to give me a periodic change.  The task is

    1. Start the shield
    2. Then wait for a new value in the queue (or the timeout)
    3. If it is a new value then change the desired value
    4. If the current value is not the same as the desired value then take a step towards the desired value
    void motor_task(void* param)
    {
        (void)param;
    
        tle9879_sys_t tle9879_sys;
    
        uint8_t numberOfBoards = 1;
        BaseType_t rtos_api_result;
    
        motor_value_q = xQueueCreate(1,sizeof(int));
    
        /* Initialize and configure the motor driver */
        tle9879sys_init(&tle9879_sys,
        					CYBSP_D11,
                            CYBSP_D12,
                            CYBSP_D13,
                            NULL,
                            CYBSP_D4,
                            CYBSP_D5,
                            CYBSP_D6,
                            CYBSP_D7,
                            &numberOfBoards);
        if (tle9879_sys.board_count == 0)
        {
            printf("Motor board not detected. Exiting Motor task.\n");
            vTaskDelete(NULL);
        }
    
        tle9879sys_setMode(&tle9879_sys, FOC, 1, false);
        
        bool motorState=false;
        int currentPercentage=0;
        int desiredPercentage=0;
    
        while(1)
        {
        	rtos_api_result = xQueueReceive(motor_value_q, &desiredPercentage, RPM_CHANGE_INTERVAL);
    
           	/* Value has been received from the queue (i.e. not a timeout) */
            if(rtos_api_result == pdTRUE)
            {
                if(desiredPercentage < RPM_PERCENT_MIN) /* Any value less than 10% will result in stopping the motor */
                    desiredPercentage = 0;
            
                if(desiredPercentage>100)
                    desiredPercentage = 100;
            }
    
            if(currentPercentage != desiredPercentage)
            {
                if(abs(currentPercentage-desiredPercentage) < RPM_CHANGE_RATE)
                    currentPercentage = desiredPercentage;
    
                if (currentPercentage < desiredPercentage)
                    currentPercentage = currentPercentage + RPM_CHANGE_RATE;
                if (currentPercentage > desiredPercentage)
                    currentPercentage = currentPercentage - RPM_CHANGE_RATE;
    
                if(currentPercentage>0 && motorState==false)
                {
                    tle9879sys_setMotorMode(&tle9879_sys, START_MOTOR, 1);
                    motorState = true;
                }
                if(currentPercentage == 0 && motorState==true)
                {
                    tle9879sys_setMotorMode(&tle9879_sys, STOP_MOTOR, 1);
                    motorState = false;
                }
    
                float motorSpeed = ((float)(currentPercentage-RPM_PERCENT_MIN))/(float)(100.0-RPM_PERCENT_MIN) * (RPM_MAX - RPM_MIN) + RPM_MIN;
    
                tle9879sys_setMotorSpeed(&tle9879_sys, motorSpeed, 1);
                printf("Current %d%% Desired=%d%% Speed=%f\n",currentPercentage,desiredPercentage,motorSpeed);
            }
        }
    }

    In order to simplify the CapSense and cloud tasks I provide a function to submit a new percent to the queue.

    void motor_update(int speed)
    {
        if(motor_value_q)
            xQueueOverwrite(motor_value_q,&speed);
    }
    

    6. Update the CapSense task to submit to the motor_task

    Lets fix the CapSense task to submit to the motor queue.  First fix the include.

    #include "motor_task.h"

    Then update the buttons to use the submit function

    if((0u != button0_status) && (0u == button0_status_prev))
    {
        printf("Button 0 pressed\n");
        motor_update(0); // Stop the Motor
    }
    
    if((0u != button1_status) && (0u == button1_status_prev))
    {
        printf("Button 1 pressed\n");
        motor_update(75); // Set the motor to 75%
    }
    

    Fix the slider to use the submit function as well.

    if((0u != slider_touched) && (slider_pos_prev != slider_pos ))
    {
        printf("Slider position %d\n",slider_pos);
        motor_update(slider_pos);
    }
    

    7. Fix the Cloud Task

    Remember that we copied our cloud task from the remote control.  It publishes MQTT messages.  We need to fix the cloud task to subscribe instead of publish.   If you recall from Lesson 4 we submit the messages as JSON.  To decode the JSON I will use a JSON parser.

    #include "cy_json_parser.h"

    The function prototypes are almost the same, except for a subscribe instead of publish, plus a JSON callback

    static void cloud_connectWifi();
    static void cloud_startMQTT();
    static void cloud_subscribeMQTT();
    static void cloud_mqtt_event_cb( cy_mqtt_t mqtt_handle, cy_mqtt_event_t event, void *user_data);
    static cy_rslt_t json_cb(cy_JSON_object_t *json_object, void *arg);
    

    The cloud task is much simpler this time.  connect, start, subscribe and then wait for some action.

    void cloud_task(void* param)
    {
        (void)param;
    
        cloud_connectWifi();
        cloud_startMQTT();
        cloud_subscribeMQTT();
    
        for(;;)
        {
            vTaskSuspend(NULL);
        }
    }

    To subscribe you just setup a subscribe info structure.  Then call the subscribe.  You also need to configure the JSON parser.

    static void cloud_subscribeMQTT()
    {
    
        cy_rslt_t result;
    
        cy_mqtt_subscribe_info_t    sub_msg[1];
    
        /* Subscribe to motor speed MQTT messages */
        sub_msg[0].qos = 0;
        sub_msg[0].topic = CLOUD_MQTT_TOPIC;
        sub_msg[0].topic_len = strlen(sub_msg[0].topic);
    
        result = cy_mqtt_subscribe( mqtthandle, sub_msg, 1 );
        CY_ASSERT(result == CY_RSLT_SUCCESS);
        printf("Subscribe Success to Topic %s\n",CLOUD_MQTT_TOPIC);
    
        /* Register JSON callback function */
        cy_JSON_parser_register_callback(json_cb, NULL);
    
    }
    

    When you get a callback from the MQTT, you will look and see which topic.  If it matches the motor topic then call the JSON parser.

    static void cloud_mqtt_event_cb( cy_mqtt_t mqtt_handle, cy_mqtt_event_t event, void *user_data )
    {
        cy_mqtt_publish_info_t *received_msg;
        printf( "\nMQTT App callback with handle : %p \n", mqtt_handle );
        (void)user_data;
        switch( event.type )
        {
            case CY_MQTT_EVENT_TYPE_DISCONNECT :
                if( event.data.reason == CY_MQTT_DISCONN_TYPE_BROKER_DOWN )
                {
                    printf( "\nCY_MQTT_DISCONN_TYPE_BROKER_DOWN .....\n" );
                }
                else
                {
                    printf( "\nCY_MQTT_DISCONN_REASON_NETWORK_DISCONNECTION .....\n" );
                }
                break;
            case CY_MQTT_EVENT_TYPE_PUBLISH_RECEIVE :
                received_msg = &(event.data.pub_msg.received_message);
                printf( "Incoming Publish Topic Name: %.*s\n", received_msg->topic_len, received_msg->topic );
                printf( "Incoming Publish message Packet Id is %u.\n", event.data.pub_msg.packet_id );
                printf( "Incoming Publish Message : %.*s.\n\n", ( int )received_msg->payload_len, ( const char * )received_msg->payload );
                if(memcmp(received_msg->topic, CLOUD_MQTT_TOPIC, strlen(CLOUD_MQTT_TOPIC)) == 0) /* Topic matches the motor speed topic */
                {
                        cy_JSON_parser(received_msg->payload, received_msg->payload_len);
                }
                
                break;
            default :
                printf( "\nUNKNOWN EVENT .....\n" );
                break;
        }
    }

    If the JSON parser matches, you call the motor_update function with the new requested speed.

    /* This is the callback from the cy_JSON_parser function. It is called whenever
     * the parser finds a JSON object. */
    static cy_rslt_t json_cb(cy_JSON_object_t *json_object, void *arg)
    {
        int motorSpeed;
    
        if(memcmp(json_object->object_string, MOTOR_KEY, json_object->object_string_length) == 0)
        {
            if(json_object->value_type == JSON_NUMBER_TYPE)
            {
                /* Add null termination to the value and then convert to a number */
                char resultString[json_object->value_length + 1];
                memcpy(resultString, json_object->value, json_object->value_length);
                resultString[json_object->value_length] = 0;
                motorSpeed = (uint8_t) atoi(resultString);
                printf("Received speed value from cloud: %d\n", motorSpeed);
                motor_update(motorSpeed);
            }
        }
        return CY_RSLT_SUCCESS;
    }
    

    8. Fix main.c to start the motor task

    In main.c you need to “do the needful” starting with an include.

    #include "motor_task.h"

    Then starting the motor task.

    xTaskCreate(motor_task,    "Motor"     ,configMINIMAL_STACK_SIZE*8  , NULL, 1, 0);
    

    9. Test

    Resources for Project

    You can find the completed project in your project creator dialog by filtering for “IoT Expert Embedded”.  This is lesson6

    You can also clone this project at git@github.com:iotexpert/ew21-lesson6.git or https://github.com/iotexpert/ew21-lesson6

    EW21: Lesson 7: WS2812 LED Strip

    Embedded World 2021 - Infineon ModusToolbox PSoC 6 Drone 

     

    Summary

    In lesson 7 we will finish this Frankenstein drone setup by adding WS2812 Strip LEDs.  At the end of lesson one you will have the “WS2812 LED” block colored green.  Here is the architecture:

    Learning Objectives

    1. Have fun by adding another cool library – WS2812

    Procedure

    1. Create a new project
    2. Add the IoT Expert WS2812 Library
    3. Modify motor_task.c
    4. Test

    1. Create a new project

    Start from the CY8CKIT-062S2-43012

    Give the project a sensible name.  In this case IoT_Expert_Embedded_World_2021_Lesson7

    2. Add the IoT Expert WS2812 Library

    You need to find the IoT Expert WS2812 library and include it into your project.

    3. Modify motor_task.c

    Include the header file for the library.

    #include "ws2812.h"

    The number of LEDs in our system

    #define		NUM_LEDS				(61)

    Define some colors to use and initialize the LED strip

    /* LED color array - 7 different colors each with RGB values */
    uint8_t ledColors[7][3] = {
            { 0,  0,  0},	// Off
            {20,  0, 30},	// Violet
            { 0,  0, 50},	// Blue
            { 0, 50,  0},	// Green
            {30, 20,  0},	// Yellow
            {42,  8,  0},	// Orange
            {50,  0,  0},	// Red
    };
    
     uint8_t ledColorRow = 0;
     uint8_t ledColorRowPrev = 0;
    /* Initialize LED strips */
    ws2812_init(NUM_LEDS, P10_0, P10_1, P10_2);
    

    A bit of code to send updates to the LED strip based on the status of the motor.

    /* Calculate LED color and update if it has changed */
                if(motorState == false)
                {
                    /* Turn off LEDs */
                    ws2812_setMultiRGB(0, NUM_LEDS-1, 0, 0, 0);
                    ws2812_update();
                    ledColorRowPrev = 0;
                }
                else
                {
                    ledColorRow = 1 + (uint8_t)((( (uint16_t)motorSpeed - (uint16_t)RPM_MIN ) * 5) / ((uint16_t)RPM_MAX - (uint16_t)RPM_MIN)); /* Determine row to use */
                    if(ledColorRowPrev != ledColorRow)
                    {
                        ws2812_setMultiRGB(0, NUM_LEDS-1, ledColors[ledColorRow][0], ledColors[ledColorRow][1], ledColors[ledColorRow][2]);
                        ws2812_update();
                        ledColorRowPrev = ledColorRow;
                    }
                }

    4. Test

    Let it rip tater chip

    Here it is with the LED strips on…

    Resources for Project

    You can find the completed project in your project creator dialog by filtering for “IoT Expert Embedded”.  This is lesson 7

    You can also clone this project at git@github.com:iotexpert/ew21-lesson2.git or https://github.com/iotexpert/ew21-lesson2