Mouser PSoC6-WiFi-BT L4 : The Video Game

Designing low-power, cloud-connected IoT devices with PSoC® 6 MCU’s and WICED® Wi-Fi/Bluetooth

Summary

In this lesson I’ll finish the video game thread by adding the graphics etc. to play the game.  In addition I’ll fix up the CapSense thread so that it is connected to the game via an RTOS queue.

There are three main things going on in this game.

  1. A state machine for the game screen (Splash, Start, Running, Over)
  2. A 20ms timer that updates the screen while the game is running (moves the Paddle and the Ball)
  3. A GUI queue where the rest of the system – CapSense,  Bluetooth and WiFi – can send Button and Paddle messages.

To implement this project I will:

  1. Setup the project and makefile by copying L3CapSenseTft
  2. Update gamethread.h to define the GUI queue messages
  3. Fix main.c to create the queue
  4. Create SystemGlobal.h to give the rest of the files access to the gui queue
  5. Updating the CapSenseThread to send GUI messages
  6. Update the includes in GameThread.c
  7. Add some #define macros to define game parameters
  8. Add a State Machine for the game & define some paddle movement methods
  9. Make forward declarations for the thread functions
  10. Create some variables to maintain game state
  11. Add display functions for the score and the speed
  12. Add functions to start and end the game
  13. Add helper functions to calculate the top and bottom of the paddle
  14. Add a function to update and draw the paddle
  15. Add a function to update and draw the ball
  16. Add a function for the game timer to call
  17. Update the main game thread

Setup the project and makefile by copying L3CapSenseTft

Use copy/paste to copy the L3CapSenseTft project to a new folder name L4Game.   Change the name of the makefile to be L4Game.mk.

Edit the makefile and change the name of the application.

NAME := App_WStudio_L4Game

$(NAME)_SOURCES := main.c \
    CapSenseThread.c \
	GameThread.c \
	cy_tft_display.c

$(NAME)_COMPONENTS := graphics/ugui

Create a make target for this project

Update GameThread.h to Define the GUI Messages

All of the threads in the system (CapSense, Bluetooth, and WiFi) will control the paddle and the button by sending messages to an RTOS queue.  In gameThread.h we will add a definition of that message.  The message is just a structure with two values – which GUI element and what value to send.

#pragma once
#include "wiced.h"

typedef enum {
    MSG_POSITION,
    MSG_BUTTON0,
    MSG_BUTTON1,
} game_evt_t;

typedef struct {
    game_evt_t evt;
    uint32_t val;
} game_msg_t;

void gameThread(wiced_thread_arg_t arg);

Fix main.c to Create the Queue

I typically believe that the RTOS primitives should be owned by the main.c.  To do this edit main.c and fix the includes.

#include "GameThread.h"
#include "wiced.h"
#include "CapSenseThread.h"
#include "SystemGlobal.h"

Then define the queue variable “paddleQueue” which I should have names “guiQueue” but it is way to late to fix it now — oh well.

/******************************************************
 *               Variable Definitions
 ******************************************************/

wiced_thread_t blinkThreadHandle;
wiced_thread_t capSenseThreadHandle;
wiced_thread_t gameThreadHandle;
wiced_queue_t paddleQueue;

Create the queue

void application_start( )
{
    wiced_init( );
    wiced_rtos_init_queue(&paddleQueue,"paddleQueue",sizeof(game_msg_t),10);
    wiced_rtos_create_thread(&blinkThreadHandle,7,"Blink Thread",pdlBlinkThread,500,0);
    wiced_rtos_create_thread(&capSenseThreadHandle,7,"CapSense Thread",capSenseThread,1024,0);
    wiced_rtos_create_thread(&gameThreadHandle,7,"game Thread",gameThread,4096,0);
 }

Create SystemGlobal.h

Each of the threads in the system need to have access to the paddleQueue.  In order to do that create a file called SystemGlobal.h and extern the variable to give them that access.

#pragma once

extern wiced_queue_t paddleQueue;

Updating the CapSenseThread to send GUI messages

Remember that when we setup the CapSenseThread originally, it just printed out the values.  Let’s fix it so send messages.  So, edit CapSenseThread.c.

  1. Add a message variable (line 8)
  2. Fix the button0 and button 1 to make and send RTOS messages (lines 20/21 and 26/27)
  3. Fix the slider to send the position (lines 33-35)
#include "wiced.h"
#include "GameThread.h"
#include "SystemGlobal.h"

void capSenseThread(wiced_thread_arg_t arg)
{

    game_msg_t msg;

    CapSense_Start();
    CapSense_ScanAllWidgets();
    while(1)
    {
        if(!CapSense_IsBusy())
        {

            CapSense_ProcessAllWidgets();
            if(CapSense_IsWidgetActive(CapSense_BUTTON0_WDGT_ID))
            {
                msg.evt = MSG_BUTTON0;
                wiced_rtos_push_to_queue(&paddleQueue,&msg,0);
            }

            if(CapSense_IsWidgetActive(CapSense_BUTTON1_WDGT_ID))
            {
                msg.evt = MSG_BUTTON1;
                wiced_rtos_push_to_queue(&paddleQueue,&msg,0);
            }

            uint32_t val = CapSense_GetCentroidPos(CapSense_LINEARSLIDER0_WDGT_ID);
            if(val < 0xFFFF)
            {
                msg.evt = MSG_POSITION;
                msg.val = val;
                wiced_rtos_push_to_queue(&paddleQueue,&msg,0);
            }
            CapSense_ScanAllWidgets();
        }
        wiced_rtos_delay_milliseconds(25); // Poll every 25ms (actual scan time ~8ms)
    }
}

Update the includes in GameThread.c

Now let’s fix GameThread.c.  Start by editing the includes to add a new file called “SystemGlobal.h” which contains the global variable for the GUI queue.

#include "GameThread.h"
#include "cy_tft_display.h"
#include "SystemGlobal.h"
#include "ugui.h"

Add some #define macros in GameThread.c

There are a number of constants which I use in the game.  In this section I use #define macros to define them.

#define UPDATE_SCREEN_TIME (20) // Update the screen every 20ms
#define SPEED (2)
#define SCREEN_X (320)
#define SCREEN_Y (240)
#define TOP_FIELD (21)
#define PD_WIDTH (10)
#define PD_LEN (70)
#define DOTS (3)
#define PADDLE0_COLOR (C_BLUE)
#define BALL_COLOR (C_GREEN)
#define BALL_SIZE (10)

Add a State Machine for the Game & Define Paddle Movement

Open up GameThread.c – all of the game control functions will go there.

There will be four screens in the game.  A splash screen to display Cypress and Mouser, a Ready Player 1 Screen, the actual game screen and a game over screen.

In addition the paddle can move a little bit at a time (increment) or jump directly to the position (absolute)

// States of the game
typedef enum {
    GS_SPLASH,
    GS_START,
    GS_RUNNING,
    GS_OVER
} game_state_t;

// Methods to move the paddle
typedef enum {
    PADDLE_INCREMENT,
    PADDLE_ABSOLUTE
} paddle_update_t;

Fix the gameState statemachine

In the splash screen I need to set the state machine to GS_SPLASH

static void displaySplashScreen()
{
    gameState = GS_SPLASH;
    UG_FontSelect( &FONT_22X36 );
    UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5,22,36,"Cypress");
    UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*2,22,36,"Mouser");
    UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*3,22,36,"PSoC 6");
    UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*4,22,36,"WICED 4343");

    wiced_rtos_delay_milliseconds(2000);
}

In the start screen I need to set the state machine to GS_START

// Display the Start Screen
static void  displayStartScreen()
{
    gameState = GS_START;
    UG_FillScreen( C_BLACK );
    UG_FontSelect( &FONT_22X36 );
    UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 -2 - 18 ,22,36,"Ready");
    UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 + 2 + 18 ,22,36,"Player 1");
    displayStartButton();
}

Make Forward Declarations for Functions

You should define the functions in advance of using them

/******************************************************
 *               Static Function Declarations
 ******************************************************/
static void UG_PutStringCenter(uint32_t x, uint32_t y, uint32_t fontx, uint32_t fonty,char *string);
static void displaySplashScreen();
static void displayStartButton();
static void  displayStartScreen();
static void displayScore();
static void displaySpeed();
static void endGame();
static inline uint32_t calcPaddleTop();
static inline uint32_t calcPaddleBottom();
static void updatePaddle(paddle_update_t type);
static void updateBall();
static void updateScreen(void *arg);

Create some variables to maintain game state

The updateScreenTimer is used while the game is running to call the updateScreen every 20ms.  The rest of the variables are self explanatory.

/******************************************************
 *               Variable Definitions
 ******************************************************/
static UG_GUI   gui;
static wiced_timer_t updateScreenTimer;

static uint32_t gameScore;
static game_state_t gameState;

// position of the paddle
static uint32_t paddle0_desire_pos=0;
static uint32_t paddle0_cur_pos=0;

// Position, direction and speed of the ball
static uint32_t ballx,bally;
static int32_t ballXdir, ballYdir;
static uint32_t ballSpeed;

Add Display Functions for the Score & Speed

These two functions print the speed and score at the top of the screen.

// This function displays the score
static void displayScore()
{
    char buff[10];
    sprintf(buff,"%2X",(unsigned int)gameScore);
    UG_FontSelect(&FONT_12X20);
    UG_PutString( 75, 0, buff);
}

// This function displays the speed
static void displaySpeed()
{
    char buff[10];
    sprintf(buff,"%2X",(unsigned int)ballSpeed-1);
    UG_FontSelect(&FONT_12X20);
    UG_PutString( 275, 0, buff);
}

Add Function to Start the Game

When the game needs to start you:

  1. Reset the score
  2. Set the paddle position
  3. Move the ball to the middle of the paddle
  4. Set the ball to move to the right and down
  5. Clear the screen, display score and speed
  6. Start the game running
// This function initializes everything and starts a new game
static void startGame()
{
    gameScore = 0;

    paddle0_desire_pos = 50; // start the game with the paddle moving
    paddle0_cur_pos = 0;

    ballx = PD_WIDTH ;                   // start the ball on the paddle on the right of the screen
    bally  = calcPaddleTop() + PD_LEN/2; // start the ball in the middle of the paddle

    ballSpeed = SPEED;

    ballXdir = ballSpeed;
    ballYdir = ballSpeed;

    UG_FillScreen( C_BLACK );  // clear screen
    UG_FontSelect(&FONT_12X20);
    UG_PutString( 0, 0,  "Score:");
    displayScore();
    UG_PutString(200,0,"Speed:");
    displaySpeed();
    UG_DrawLine(0,20,SCREEN_X,20,C_RED); // red line under text to represent top of play screen

    gameState = GS_RUNNING;
    wiced_rtos_start_timer(&updateScreenTimer); // Timer to update screen
}

Add Function to End the Game

When the game is over you should:

  1. Move the game state to over
  2. Stop the timer
  3. Display game over
  4. Display press button 0 to start
// Stop the game
static void endGame()
{
    gameState = GS_OVER;
    wiced_rtos_stop_timer(&updateScreenTimer);
    UG_FontSelect( &FONT_22X36 );
    UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2,22,36,"Game Over");
    displayStartButton();
}

Add Helper Functions to Calculate Paddle Top & Bottom

There are two places where you need to know the position of the Paddle.  Specifically:

  1. To draw the paddle
  2. To figure out if the ball hit the paddle or not.

These two functions calculate the pixel position of the top and bottom of the paddle based on it current position

// Figure out the y position of the top of the paddle
static inline uint32_t calcPaddleTop()
{
    return (paddle0_cur_pos)*DOTS+TOP_FIELD;
}

// Figure out the y position of the bottom of the paddle
static inline uint32_t calcPaddleBottom()
{
    return (paddle0_cur_pos)*DOTS+PD_LEN+TOP_FIELD;
}

Add a Function to Update & Draw the Paddle

While the game is running you need the paddle to move.  There are two methods:

  1. Absolute just moves the current position immediately to the desired position.
  2. Incremental, which moves the paddle a little bit towards the desired position.
// Move the paddle either to : PADDLE_INCREMENT the next location or PADDLE_ABSOLUTE - final location
static void updatePaddle(paddle_update_t type)
{
    // If the paddle is where it is supposed to be then just return
    if(paddle0_cur_pos == paddle0_desire_pos)
        return;

    // erase the current paddle
    UG_FillFrame(0,calcPaddleTop(),PD_WIDTH,calcPaddleBottom(),C_BLACK);

    switch (type)
    {

    case PADDLE_INCREMENT:

        if(paddle0_cur_pos < paddle0_desire_pos)
            paddle0_cur_pos += SPEED;
        else
            paddle0_cur_pos -= SPEED;

        // If the paddle is within one move of the final spot, put it there
        if(abs((int)paddle0_cur_pos-(int)paddle0_desire_pos) < SPEED)
            paddle0_cur_pos = paddle0_desire_pos;
        break;

    case PADDLE_ABSOLUTE:
        paddle0_cur_pos = paddle0_desire_pos;
        break;
    }
    // draw the paddle
    UG_FillFrame(0,calcPaddleTop(),PD_WIDTH,calcPaddleBottom(),PADDLE0_COLOR);
}

Add a function to update and draw the ball

You need a function to:

  1. Move the ball
  2. Figure out if it hit the right/left/top/bottom of the screen and do the right thing.

When the ball hits one of those surfaces it needs to change direction to either plus or minus.

Every time it hits the paddle the score should increase and possibly speed up.

If it misses the paddle the game is over.

// Move the ball to the next location
static void updateBall()
{
    static const uint32_t BallFudgeFactor=3;

    UG_DrawCircle(ballx,bally,BALL_SIZE,C_BLACK);

    ballx += ballXdir;
    bally += ballYdir;

    // Check to see if the ball hit the far right side
    if(ballx > SCREEN_X - BALL_SIZE)
    {

        ballx = SCREEN_X - BALL_SIZE;
        ballXdir = -ballSpeed;
    }

    // check to see if the ball hit the far left side... or the paddle
    if(ballx < (BALL_SIZE + PD_WIDTH + BallFudgeFactor))
    {
        // See if the ball missed the paddle
        if(bally + BALL_SIZE < calcPaddleTop() || bally - BALL_SIZE > calcPaddleBottom())
        {
            endGame();
            //WPRINT_APP_INFO(("Missed Paddle\r\n"));
        }

        gameScore = gameScore + 1;
        displayScore();
        if(gameScore % 3 == 0) // Speed up every three hits
        {
            ballSpeed +=1;
            displaySpeed();
        }

        ballx = BALL_SIZE + PD_WIDTH + BallFudgeFactor;
        ballXdir = +ballSpeed;
    }
    // Check to see if the ball hit the top or bottom
    if(bally > SCREEN_Y - BALL_SIZE) // bottom
    {
        bally = SCREEN_Y - BALL_SIZE;
        ballYdir = -ballSpeed;
    }

    if(bally < TOP_FIELD+BALL_SIZE) // top
    {
        bally = BALL_SIZE+TOP_FIELD;
        ballYdir = +ballSpeed;
    }
    UG_DrawCircle(ballx,bally,BALL_SIZE,BALL_COLOR);
}

Create a Function for the Game Timer

An RTOS timer runs every 20ms.  That timer needs a function to move the paddle and move the ball.

// This function is called every UPADTE_SCREEN_TIME milliseconds by the updateScreenTimer
static void updateScreen(void *arg)
{
    updatePaddle(PADDLE_INCREMENT);
    updateBall();
}

Update the Main Game Thread

The main game thread needs to get messages out of the queue and then do the right thing based on the game state.

// Main game thread
void gameThread(wiced_thread_arg_t arg)
{
    game_msg_t msg;

    Cy_TFT_Init();                                             // Init the TFT
    UG_Init( &gui, Cy_TFT_displayDriver, SCREEN_X, SCREEN_Y ); // Connect the driver

    UG_FillScreen( C_BLACK );   // Clear the screen
    UG_SetBackcolor( C_BLACK );
    UG_SetForecolor( C_WHITE );

    wiced_rtos_init_timer(&updateScreenTimer,UPDATE_SCREEN_TIME,updateScreen,0);
    displaySplashScreen();
    displayStartScreen();

    while(1)
    {
        wiced_rtos_pop_from_queue(&paddleQueue,&msg,WICED_WAIT_FOREVER);
        switch(msg.evt)
        {
        case MSG_POSITION:
            if(gameState == GS_RUNNING)
                paddle0_desire_pos = msg.val/2;
            if(gameState == GS_OVER)
            {
                paddle0_desire_pos = msg.val/2;
                updatePaddle(PADDLE_ABSOLUTE);
            }
            break;

        case MSG_BUTTON0:
            if(gameState == GS_OVER || gameState == GS_START)
                startGame();
            break;
        case MSG_BUTTON1:
            break;
        }
    }
}

Program and Test

Now that it is all done… program and test it.

GameThread.c

Here is the whole thread is here so you can copy/paste it into your file.

#include "GameThread.h"
#include "cy_tft_display.h"
#include "SystemGlobal.h"
#include "ugui.h"
/******************************************************
*                      Macros
******************************************************/
#define UPDATE_SCREEN_TIME (20) // Update the screen every 20ms
#define SPEED (2)
#define SCREEN_X (320)
#define SCREEN_Y (240)
#define TOP_FIELD (21)
#define PD_WIDTH (10)
#define PD_LEN (70)
#define DOTS (3)
#define PADDLE0_COLOR (C_BLUE)
#define BALL_COLOR (C_GREEN)
#define BALL_SIZE (10)
/******************************************************
*                    Constants
******************************************************/
/******************************************************
*                   Enumerations
******************************************************/
/******************************************************
*                 Type Definitions
******************************************************/
// States of the game
typedef enum {
GS_SPLASH,
GS_START,
GS_RUNNING,
GS_OVER
} game_state_t;
// Methods to move the paddle
typedef enum {
PADDLE_INCREMENT,
PADDLE_ABSOLUTE
} paddle_update_t;
/******************************************************
*                    Structures
******************************************************/
/******************************************************
*               Static Function Declarations
******************************************************/
static void UG_PutStringCenter(uint32_t x, uint32_t y, uint32_t fontx, uint32_t fonty,char *string);
static void displaySplashScreen();
static void displayStartButton();
static void  displayStartScreen();
static void displayScore();
static void displaySpeed();
static void endGame();
static inline uint32_t calcPaddleTop();
static inline uint32_t calcPaddleBottom();
static void updatePaddle(paddle_update_t type);
static void updateBall();
static void updateScreen(void *arg);
/******************************************************
*               Variable Definitions
******************************************************/
static UG_GUI   gui;
static wiced_timer_t updateScreenTimer;
static uint32_t gameScore;
static game_state_t gameState;
// position of the paddle
static uint32_t paddle0_desire_pos=0;
static uint32_t paddle0_cur_pos=0;
// Position, direction and speed of the ball
static uint32_t ballx,bally;
static int32_t ballXdir, ballYdir;
static uint32_t ballSpeed;
/******************************************************
*               Functions
******************************************************/
// ARH Function to put text in the center of a point (UG_PutString does upper left)
static void UG_PutStringCenter(uint32_t x, uint32_t y, uint32_t fontx, uint32_t fonty,char *string)
{
y = y - fonty/2;
x = x - (strlen(string)/2)*fontx;
if(strlen(string)%2)
x = x - fontx/2;
UG_PutString(x,y,string);
}
// Display the splash screen
static void displaySplashScreen()
{
gameState = GS_SPLASH;
UG_FontSelect( &FONT_22X36 );
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5,22,36,"Cypress");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*2,22,36,"Mouser");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*3,22,36,"PSoC 6");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*4,22,36,"WICED 4343");
wiced_rtos_delay_milliseconds(2000);
}
// This function displays the start button message
static void displayStartButton()
{
UG_FontSelect(&FONT_12X20);
UG_PutStringCenter(SCREEN_X/2 , SCREEN_Y - 30 ,12,22,  "Press B0 To Start");
}
// Display the Start Screen
static void  displayStartScreen()
{
gameState = GS_START;
UG_FillScreen( C_BLACK );
UG_FontSelect( &FONT_22X36 );
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 -2 - 18 ,22,36,"Ready");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 + 2 + 18 ,22,36,"Player 1");
displayStartButton();
}
// This function displays the score
static void displayScore()
{
char buff[10];
sprintf(buff,"%2X",(unsigned int)gameScore);
UG_FontSelect(&FONT_12X20);
UG_PutString( 75, 0, buff);
}
// This function displays the speed
static void displaySpeed()
{
char buff[10];
sprintf(buff,"%2X",(unsigned int)ballSpeed-1);
UG_FontSelect(&FONT_12X20);
UG_PutString( 275, 0, buff);
}
// This function initializes everything and starts a new game
static void startGame()
{
gameScore = 0;
paddle0_desire_pos = 50; // start the game with the paddle moving
paddle0_cur_pos = 0;
ballx = PD_WIDTH ;                   // start the ball on the paddle on the right of the screen
bally  = calcPaddleTop() + PD_LEN/2; // start the ball in the middle of the paddle
ballSpeed = SPEED;
ballXdir = ballSpeed;
ballYdir = ballSpeed;
UG_FillScreen( C_BLACK );  // clear screen
UG_FontSelect(&FONT_12X20);
UG_PutString( 0, 0,  "Score:");
displayScore();
UG_PutString(200,0,"Speed:");
displaySpeed();
UG_DrawLine(0,20,SCREEN_X,20,C_RED); // red line under text to represent top of play screen
gameState = GS_RUNNING;
wiced_rtos_start_timer(&updateScreenTimer); // Timer to update screen
}
// Stop the game
static void endGame()
{
gameState = GS_OVER;
wiced_rtos_stop_timer(&updateScreenTimer);
UG_FontSelect( &FONT_22X36 );
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2,22,36,"Game Over");
displayStartButton();
}
// Figure out the y position of the top of the paddle
static inline uint32_t calcPaddleTop()
{
return (paddle0_cur_pos)*DOTS+TOP_FIELD;
}
// Figure out the y position of the bottom of the paddle
static inline uint32_t calcPaddleBottom()
{
return (paddle0_cur_pos)*DOTS+PD_LEN+TOP_FIELD;
}
// Move the paddle either to : PADDLE_INCREMENT the next location or PADDLE_ABSOLUTE - final location
static void updatePaddle(paddle_update_t type)
{
// If the paddle is where it is supposed to be then just return
if(paddle0_cur_pos == paddle0_desire_pos)
return;
// erase the current paddle
UG_FillFrame(0,calcPaddleTop(),PD_WIDTH,calcPaddleBottom(),C_BLACK);
switch (type)
{
case PADDLE_INCREMENT:
if(paddle0_cur_pos < paddle0_desire_pos)
paddle0_cur_pos += SPEED;
else
paddle0_cur_pos -= SPEED;
// If the paddle is within one move of the final spot, put it there
if(abs((int)paddle0_cur_pos-(int)paddle0_desire_pos) < SPEED)
paddle0_cur_pos = paddle0_desire_pos;
break;
case PADDLE_ABSOLUTE:
paddle0_cur_pos = paddle0_desire_pos;
break;
}
// draw the paddle
UG_FillFrame(0,calcPaddleTop(),PD_WIDTH,calcPaddleBottom(),PADDLE0_COLOR);
}
// Move the ball to the next location
static void updateBall()
{
static const uint32_t BallFudgeFactor=3;
UG_DrawCircle(ballx,bally,BALL_SIZE,C_BLACK);
ballx += ballXdir;
bally += ballYdir;
// Check to see if the ball hit the far right side
if(ballx > SCREEN_X - BALL_SIZE)
{
ballx = SCREEN_X - BALL_SIZE;
ballXdir = -ballSpeed;
}
// check to see if the ball hit the far left side... or the paddle
if(ballx < (BALL_SIZE + PD_WIDTH + BallFudgeFactor))
{
// See if the ball missed the paddle
if(bally + BALL_SIZE < calcPaddleTop() || bally - BALL_SIZE > calcPaddleBottom())
{
endGame();
//WPRINT_APP_INFO(("Missed Paddle\r\n"));
}
gameScore = gameScore + 1;
displayScore();
if(gameScore % 3 == 0) // Speed up every three hits
{
ballSpeed +=1;
displaySpeed();
}
ballx = BALL_SIZE + PD_WIDTH + BallFudgeFactor;
ballXdir = +ballSpeed;
}
// Check to see if the ball hit the top or bottom
if(bally > SCREEN_Y - BALL_SIZE) // bottom
{
bally = SCREEN_Y - BALL_SIZE;
ballYdir = -ballSpeed;
}
if(bally < TOP_FIELD+BALL_SIZE) // top
{
bally = BALL_SIZE+TOP_FIELD;
ballYdir = +ballSpeed;
}
UG_DrawCircle(ballx,bally,BALL_SIZE,BALL_COLOR);
}
// This function is called every UPADTE_SCREEN_TIME milliseconds by the updateScreenTimer
static void updateScreen(void *arg)
{
updatePaddle(PADDLE_INCREMENT);
updateBall();
}
// Main game thread
void gameThread(wiced_thread_arg_t arg)
{
game_msg_t msg;
Cy_TFT_Init();                                             // Init the TFT
UG_Init( &gui, Cy_TFT_displayDriver, SCREEN_X, SCREEN_Y ); // Connect the driver
UG_FillScreen( C_BLACK );   // Clear the screen
UG_SetBackcolor( C_BLACK );
UG_SetForecolor( C_WHITE );
wiced_rtos_init_timer(&updateScreenTimer,UPDATE_SCREEN_TIME,updateScreen,0);
displaySplashScreen();
displayStartScreen();
while(1)
{
wiced_rtos_pop_from_queue(&paddleQueue,&msg,WICED_WAIT_FOREVER);
switch(msg.evt)
{
case MSG_POSITION:
if(gameState == GS_RUNNING)
paddle0_desire_pos = msg.val/2;
if(gameState == GS_OVER)
{
paddle0_desire_pos = msg.val/2;
updatePaddle(PADDLE_ABSOLUTE);
}
break;
case MSG_BUTTON0:
if(gameState == GS_OVER || gameState == GS_START)
startGame();
break;
case MSG_BUTTON1:
break;
}
}
}

 

Mouser PSoC 6-WiFi-BT L3: Using the CY8CKIT-028-TFT Shield

Designing low-power, cloud-connected IoT devices with PSoC® 6 MCU’s and WICED® Wi-Fi/Bluetooth

Summary

In this lesson we will start making the game.  The first thing that it will need is a display and we will use the CY8CKIT-028-TFT.  In order to talk to the display we will use a library built into WICED called ugui.  That library needs a driver configuration which we will copy of out the code example we provide.  Finally we will start building a thread called the “GameThread” which will actually make up the game.

  1. Download CE222494_PSoC6_WICED_WiFi
  2. Copy the L2CapSense into L3CapSenseTft
  3. Copy cy_tft_display.c/h into the project
  4. Make a file GameThread.h
  5. Make a file GameThread.c
  6. Rename L2CapSense.mk to be L3CapSenseTft.mk & Fix
  7. Update main.c
  8. Test

Download CE222494_PSoC6_WICED_WiFi

If you click on the CY8CKIT-062-WiFi-BT webpage you will find that there are a bunch of files which are associated with the development kit, including CY8CKIT-062-WiFi-BT PSoC® 6 WiFi-BT Pioneer Kit Code Examples.zip.

Download that folder, then copy the directory into your WICED Studio Apps/WStudio folder.

Once you do that it should look like this:

Copy L3CapSense into L3CapSenseTft

Now copy/paste the L2CapSense project into a new project called L3CapSenseTft

Copy cy_tft_display.c/h into the project

Open up the CE222494 code example directory and copy the two files cy_tft_display.c andcy_tft_display.c which are drivers for the ugui library and then paste them into your new project L3CapSenseTft.

Make a file GameThread.h

Create a new file called GamThread.h and a definition of the GameThread which will be used by the main.c to get the game thread going.

#pragma once
#include "wiced.h"
void gameThread(wiced_thread_arg_t arg);

Make a file GameThread.c

Now create a file called GameThread.c it will have 5 functions.  Here is the whole file to make it simpler to copy and paste, but Ill explain each function one by one

#include "GameThread.h"
#include "cy_tft_display.h"
#define SCREEN_X (320)
#define SCREEN_Y (240)
static UG_GUI   gui;
// ARH Function to put text in the center of a point (UG_PutString does upper left)
static void UG_PutStringCenter(uint32_t x, uint32_t y, uint32_t fontx, uint32_t fonty,char *string)
{
y = y - fonty/2;
x = x - (strlen(string)/2)*fontx;
if(strlen(string)%2)
x = x - fontx/2;
UG_PutString(x,y,string);
}
// Display the splash screen
static void displaySplashScreen()
{
UG_FontSelect( &FONT_22X36 );
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5,22,36,"Cypress");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*2,22,36,"Mouser");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*3,22,36,"PSoC 6");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*4,22,36,"WICED 4343");
wiced_rtos_delay_milliseconds(2000);
}
// This function displays the start button message
static void displayStartButton()
{
UG_FontSelect(&FONT_12X20);
UG_PutStringCenter(SCREEN_X/2 , SCREEN_Y - 30 ,12,22,  "Press B0 To Start");
}
// Display the Start Screen
static void  displayStartScreen()
{
UG_FillScreen( C_BLACK );
UG_FontSelect( &FONT_22X36 );
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 -2 - 18 ,22,36,"Ready");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 + 2 + 18 ,22,36,"Player 1");
displayStartButton();
}
// Main game thread
void gameThread(wiced_thread_arg_t arg)
{
Cy_TFT_Init();                                             // Init the TFT
UG_Init( &gui, Cy_TFT_displayDriver, SCREEN_X, SCREEN_Y ); // Connect the driver
UG_FillScreen( C_BLACK );   // Clear the screen
UG_SetBackcolor( C_BLACK );
UG_SetForecolor( C_WHITE );
displaySplashScreen();
displayStartScreen();
while(1)
{
wiced_rtos_delay_milliseconds(1000);
}
}

The main game thread function is: void gameThread(wiced_thread_arg_t arg).  This function

  1. Initializes the TFT
  2. Initializes the UGUI library
  3. Clears the screen (by setting it all black)
  4. Sets the colors to draw white on black
  5. Displays the splash screen (which takes 2 seconds)
  6. Displays the start screen
  7. Waits until the end of time
// Main game thread
void gameThread(wiced_thread_arg_t arg)
{
Cy_TFT_Init();                                             // Init the TFT
UG_Init( &gui, Cy_TFT_displayDriver, SCREEN_X, SCREEN_Y ); // Connect the driver
UG_FillScreen( C_BLACK );   // Clear the screen
UG_SetBackcolor( C_BLACK );
UG_SetForecolor( C_WHITE );
displaySplashScreen();
displayStartScreen();
while(1)
{
wiced_rtos_delay_milliseconds(1000);
}
}

The function displaySplashScreen simply sets the font, then draws 4 text strings, then waits for a few seconds… then moves on

// Display the splash screen
static void displaySplashScreen()
{
UG_FontSelect( &FONT_22X36 );
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5,22,36,"Cypress");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*2,22,36,"Mouser");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*3,22,36,"PSoC 6");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/5*4,22,36,"WICED 4343");
wiced_rtos_delay_milliseconds(2000);
}

The displayStartScreen put the “Ready Player 1 on the screen” and then tells the user to press the B0 to start the game.

// This function displays the start button message
static void displayStartButton()
{
UG_FontSelect(&FONT_12X20);
UG_PutStringCenter(SCREEN_X/2 , SCREEN_Y - 30 ,12,22,  "Press B0 To Start");
}
// Display the Start Screen
static void  displayStartScreen()
{
UG_FillScreen( C_BLACK );
UG_FontSelect( &FONT_22X36 );
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 -2 - 18 ,22,36,"Ready");
UG_PutStringCenter(SCREEN_X/2,SCREEN_Y/2 + 2 + 18 ,22,36,"Player 1");
displayStartButton();
}

The U8G_PutString function uses coordinates x and y to set the upper left of the text.  For formatting purposes it is easier for me to think about the middle of the string.  This function just calculates the upper left (x,y) given the middle center (x,y).  To do this you need to also know the (x,y) size of the font.

static void UG_PutStringCenter(uint32_t x, uint32_t y, uint32_t fontx, uint32_t fonty,char *string)

// ARH Function to put text in the center of a point (UG_PutString does upper left)
static void UG_PutStringCenter(uint32_t x, uint32_t y, uint32_t fontx, uint32_t fonty,char *string)
{
y = y - fonty/2;
x = x - (strlen(string)/2)*fontx;
if(strlen(string)%2)
x = x - fontx/2;
UG_PutString(x,y,string);
}

Rename L2CapSense.mk to be L3CapSenseTft.mk & Fix

To make this build we need to modify the makefile to know about the new thread as well as the tft driver.  In addition we need to tell the linker to link with the graphics library.

NAME := App_WStudio_L3CapSenseTft
$(NAME)_SOURCES := 	main.c \
CapSenseThread.c \
GameThread.c \
cy_tft_display.c
$(NAME)_COMPONENTS := graphics/ugui

Update main.c

In main.c I will:

  1. Include the GameThread.h
  2. Add a variable to hold the gameThreadHandle
  3. Then start the gameThread
#include "wiced.h"
#include "CapSenseThread.h"
#include "GameThread.h"
/******************************************************
*                      Macros
******************************************************/
/******************************************************
*                    Constants
******************************************************/
/******************************************************
*                   Enumerations
******************************************************/
/******************************************************
*                 Type Definitions
******************************************************/
/******************************************************
*                    Structures
******************************************************/
/******************************************************
*               Static Function Declarations
******************************************************/
/******************************************************
*               Variable Definitions
******************************************************/
wiced_thread_t blinkThreadHandle;
wiced_thread_t capSenseThreadHandle;
wiced_thread_t gameThreadHandle;
/******************************************************
*               Function Definitions
******************************************************/
void pdlBlinkThread(wiced_thread_arg_t arg)
{
while(1)
{
Cy_GPIO_Inv(GPIO_PRT0,3);
wiced_rtos_delay_milliseconds(500);
}
}
void application_start( )
{
wiced_init();
WPRINT_APP_INFO(("Started Application\n"));
wiced_rtos_create_thread(&blinkThreadHandle,7,"Blink Thread",pdlBlinkThread,500,0);
wiced_rtos_create_thread(&capSenseThreadHandle,7,"CapSense Thread",capSenseThread,1024,0);
wiced_rtos_create_thread(&gameThreadHandle,7,"game Thread",gameThread,4096,0);
}

Test

Now it is ready to test.  So create a Make Target, then Build and Program.  Hopefully you are now Ready Player 1.

 

Mouser PSoC 6-WiFi-BT L2 : WICED Studio & CapSense

Designing low-power, cloud-connected IoT devices with PSoC® 6 MCU’s and WICED® Wi-Fi/Bluetooth

Summary

In this lesson we will build your first WICED Studio project (the blinking LED)  and make sure that you can program the development kit.  Then we will update the project to include a thread for managing the CapSense block.  This thread will be carried into the other projects.

To implement this lesson I will follow these steps:

  1. Start WICED Studio 6.2
  2. Select 43xxx
  3. Create a folder called L2CapSense
  4. Create main.c and build a blinking LED thread
  5. Create L2CapSense.mk
  6. Create a make target
  7. Build Program and test it
  8. Create CapSenseThread.c
  9. Create CapSenseThread.h
  10. Update main.c
  11. Update the makefile
  12. Build Program and Test

Create the L2CapSense Folder

Create main.c

Right click on the folder and create a new file.  Name it L2CapSense

Insert the blinking LED code into main.c

#include "wiced.h"
/******************************************************
*                      Macros
******************************************************/
/******************************************************
*                    Constants
******************************************************/
/******************************************************
*                   Enumerations
******************************************************/
/******************************************************
*                 Type Definitions
******************************************************/
/******************************************************
*                    Structures
******************************************************/
/******************************************************
*               Static Function Declarations
******************************************************/
/******************************************************
*               Variable Definitions
******************************************************/
wiced_thread_t blinkThreadHandle;
/******************************************************
*               Function Definitions
******************************************************/
void pdlBlinkThread(wiced_thread_arg_t arg)
{
while(1)
{
Cy_GPIO_Inv(GPIO_PRT0,3);
wiced_rtos_delay_milliseconds(500);
}
}
void application_start( )
{
WPRINT_APP_INFO(("Started Application\n"));
wiced_rtos_create_thread(&blinkThreadHandle,7,"Blink Thread",pdlBlinkThread,500,0);
}

Create L2CapSense.mk

Create a makefile called L2CapSense.mk

Put the build information into the L2CapSense.mk

NAME := App_WStudio_L2CapSense
$(NAME)_SOURCES := 	main.c

Create a Make Target to run the project

Build and Test the Blinking LED

Create/Edit a File called CapSenseThread.c

#include "wiced.h"
void capSenseThread(wiced_thread_arg_t arg)
{
CapSense_Start();
CapSense_ScanAllWidgets();
while(1)
{
if(!CapSense_IsBusy())
{
CapSense_ProcessAllWidgets();
if(CapSense_IsWidgetActive(CapSense_BUTTON0_WDGT_ID))
{
WPRINT_APP_INFO(("Button 0 Active\n"));
}
if(CapSense_IsWidgetActive(CapSense_BUTTON1_WDGT_ID))
{
WPRINT_APP_INFO(("Button 1 Active\n"));
}
uint32_t val = CapSense_GetCentroidPos(CapSense_LINEARSLIDER0_WDGT_ID);
if(val < 0xFFFF)
{
WPRINT_APP_INFO(("Slider = %d\n",(int)val));
}
CapSense_ScanAllWidgets();
}
wiced_rtos_delay_milliseconds(25); // Poll every 25ms (actual scan time ~8ms)
}
}

Create/Edit a File Called CapSenseThread.h

#pragma once
#include "wiced.h"
void capSenseThread(wiced_thread_arg_t arg);

Update main.c

#include "wiced.h"
#include "CapSenseThread.h"

Add a variable to hold the handle for the capSenseThread at the top of main.c

wiced_thread_t capSenseThreadHandle;

Update the main function to start the CapSenseThread

void application_start( )
{
WPRINT_APP_INFO(("Started Application\n"));
wiced_rtos_create_thread(&blinkThreadHandle,7,"Blink Thread",pdlBlinkThread,500,0);
wiced_rtos_create_thread(&capSenseThreadHandle,7,"CapSense Thread",capSenseThread,1024,0);
}

Update the L2CapsenseThread.mk

NAME := App_WStudio_L2CapSense
$(NAME)_SOURCES := 	main.c \
CapSenseThread.c

Build, Program and Test the CapSenseThread

 

 

Mouser PSoC 6-WiFi-BT L1 : Developer Resources

Designing low-power, cloud-connected IoT devices with PSoC® 6 MCU’s and WICED® Wi-Fi/Bluetooth

Summary

This is an index of links to all of the PSoC 6 & CYW4343W learning resources.  You can click the links to go the website or see screen captures of the resources.

  1. PSoC 6 Product Page
  2. WiFi + Bluetooth Combo Product Page
  3. PSoC 6 Documentation
  4. PSoC 6 Community
  5. Wireless Combo Community
  6. CY8CKIT-062-BT-WiFi Development Kit Product Page
  7. CY8CKIT-062-BT-WiFi Development Kit Guide
  8. PSoC 6 Datasheet
  9. CYW4343W Datasheet
  10. PSoC 6 Technical Reference Manuals
  11. PSoC 6 Application Notes
  12. WiFi + Bluetooth Combo Application Notes
  13. PSoC 6 Code Examples
  14. Video Tutorials
  15. PSoC 6 Knowledge Base
  16. Peripheral Driver Library Documentation (Doxygen)
  17. WICED Documentation

PSoC 6 Product Page

You can find the PSoC 6 Product landing page for PSoC 6 here

WiFi + Bluetooth Combo Page

PSoC 6 Documentation

On the PSoC 6 Product Landing page there is a documentation tab that has links to all of the current documentation.

PSoC 6 Community

Cypress has an active development community and forum.  It can be found here.

Wireless WiFi + Bluetooth Combo Community

CY8CKIT-062-WiFi-BT Development Kit Web Page

Every Cypress development kit has a web page that contains all of the information about it, including links to the documentation and store.  The CY8CKIT-062-WiFi-BT kit page is here.

CY8CKIT-062-WiFi-BT Development Kit Guide

You can find the development kit guide here.

 

PSoC 6 Datasheet

The PSoC 6 Datasheet is available on Cypress.com here.

 

CYW4343W Datasheet

The CYW4343W datasheet can be found here.

PSoC 6 Technical Reference Manual

Each of the PSoC 6 devices has a lengthy discussion of the Technical Resources.  These documents can be found here

PSoC 6 Application Notes

You can get them all on our website… here is a link to the filtered list of PSoC 6 Application Notes.

The best application note is always the “Getting Started”.  In this case it is AN210781 “Getting Started with PSoC 6 MCU with Bluetooth Low Energy (BLE) Connectivity”

WiFi + Bluetooth Combo Application Notes

Here is a link to all of the WiFI Bluetooth Combo Application Notes.

PSoC 6 Code Examples

You can find all of the PSoC 6 code examples on the web.  In addition they are built into PSoC Creator.

Or in PSoC Creator:

Videos

Cypress has made a bunch of videos that take you step by step through an introduction to PSoC 6.  You can find them on the Cypress training website.

PSoC 6 Knowledge Base

The Cypress technical support team writes “Knowledge Base” articles when there are repeated issues reported by customers.  You can find them here.

Peripheral Driver Library Documentation (Doxygen)

All of the APIs in the PDL are documented in a Doxygen generated HTML document.  You can get there from

  • Help -> Peripheral Driver Library (this link is live only when you have a PSoC 6 project open)
  • Right click on a component -> Open PDL Documentation

Mouser PSoC 6-WiFi-BT L0 : Designing low-power, cloud-connected IoT devices with PSoC® 6 MCU’s and WICED® Wi-Fi/Bluetooth

Summary

Hello everyone.  This is lesson 0 of a series of 9 lessons about creating applications for the Cypress CY8CKIT-062-WiFi-BLE development kit.  The marketing guys call the class “Designing low-power, cloud-connected IoT devices with PSoC® 6 MCU’s and WICED® Wi-Fi/Bluetooth” … which although a really long name, is a good description of what we are going to do.

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 Ill 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 Ill 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.

Todays virtual workshop is going to go like this.  Every lesson will have this table in it and you will be able to click to the right place.

Designing low-power, cloud-connected IoT devices with PSoC® 6 MCU’s and WICED® Wi-Fi/Bluetooth

You will need a few things for this class:

  • CY8CKIT-062-WiFi-BT
  • WICED Studio 6.2
  • CySmart or LightBlue a BLE
  • GoBle an iOS Remote Control App
  • An Amazon AWS IoT Account
  • A WiFi Access Point connected to the Internet

CY8CKIT-062-WiFi-BT

All of the projects in this series of lesson will be built to be programmed onto a CY8CKIT-062-WiFi-BT.  You can get the development kit from Mouser.  This development kit has a bunch of cool stuff including

  • PSoC 6 – The lowest power, most secure MCU for the IoT
  • CYW4343W – A WICED WiFi Bluetooth Combo Radio
  • S25FL512SAGMFI011 – A Cypress 512Mb Quad SPI Flash
  • CCG2 – A Cypress Type C Controller to manage Power

The CY8CKIT-028-TFT a full color 320×240 TFT display with an ST7789S display controller.

Here is the whole thing together

WICED Studio 6.2

This class is build around WICED Studio 6.2, the Cypress IDE built on top of Eclipse.  WICED Studio has all of the tools, examples and SDKs to build projects for the Cypress WICED Bluetooth and WiFi products.  We support Windows, Mac and Linux and you can download it from our community website: https://community.cypress.com/community/wireless (which I hope you have done by now)

CySmart or LightBlue a BLE

CySmart is a BLE GATT Browser which you can get from the iOS App store or the Google Play store

 

 

GoBle an iOS Remote Control App

GoBle is a Robot Remote Control App which can be downloaded from the iOS App Store.

An Amazon AWS IoT Account

In order to connect Amazon AWS IoT you will need to create an Amazon AWS Account.  It is essentially free to test you simple applications (obviously there are fees if you deploy many devices)

 

Mouser Video Training Workshop – October 24

On October 24, I will be teaching a Mouser video training workshop … live streaming on the internet.  The workshop will be about PSoC, WICED, WiFi and Bluetooth.  I am going to show PSoC Creator, WICED Studio and Cypress’s new development tool Modus Toolbox.

You can register for the workshop at this link.

I am going to build the whole class around the CY8CKIT-062-BLE-WiFi Development Kit.  You can get the devlopment kit from Mouser.

Here is the introduction: