You are here

Technical | Cypress Semiconductor

May 12, 2018

PDL Examples - SysPM Deepsleep Callbacks

In addition to the usual peripheral drivers, like PWMs and pins, which I have covered in previous articles, the PDL has platform drivers like SysPM, SysLib and SysInt. These drivers are collections of functions that operate on the PSoC system as a whole. I already (rather sneakily) wrote about SysInt - it is the interrupt handling driver that I used to play around with pin inputs. I am going to use that driver again to show you some interesting features of SysPM, the Power Management driver.

SysPM gives you control over the device (and CPU core) power modes, voltage sources and regulators, and low power callbacks. Today I am going to talk about the callbacks. When you want an application to go into a low power mode you need to think a little bit about the impact of that on the peripherals. Do they stop working or continue? Do they retain state? Do they just pick up where they left off? The behavior is all documented in the device reference manuals, of course, and the PDL provides sensible default actions via callbacks. The callbacks are just functions that get called before the device goes into, and out of, a low power mode. But what if you want to change that default? Well, maybe I shall get into that next week, but first allow me to explain how the call back system works.

Let's use a simple but realistic example. Imagine you are logging data across a slow serial line and want to go into deepsleep without dropping messages. To do that I configure a UART component to run at 1200 baud. That is slow enough that you can see the characters get written to a terminal emulator (at just over 100 characters per second). It is also definitely slow enough that I can print a string of Shakspearean prose and get my program into its pajamas and off to bed-fordshire before the end of the first stanza. No, I don't know what a stanza is either, but I can remember that they were long enough for me to fall asleep in high school...

Before staring to write the application, though, I remembered that I am using a dual core device and that it will only enter deepsleep if BOTH the CM4 and CM0+ cores are stopped. To make that happen I added one line of code to the main_cm0p.c file, like this:

    for(;;)
    {
        Cy_SysPm_DeepSleep( CY_SYSPM_WAIT_FOR_INTERRUPT );
    }

In my PSoC schematic I added a UART component and configured it for 1200 baud. I set the pins to route through the kitprog debug chip so I can see the output in my terminal emulator.

PSoC Creator UART Component Customizer     PSoC creator Pin Editor (CYDWR)

In the C code I initialize the UART and steal some code from a previous blog to generate an interrupt when I press the SW2 button. Note that the ISR does nothing - it only serves to bring the device out of low power mode. Just before the main loop I send a nice cultured message (waiting 2s for the printing to complete) and then enter the loop. Then I start to print the message again, waits 100ms, and go into deepsleep.

#include "project.h"
#define SW2_PORT            GPIO_PRT0
#define SW2_NUM             4
#define SW2_IRQ_NUM         ioss_interrupts_gpio_0_IRQn
#define SW2_IRQ_PRIORITY    3

const cy_stc_sysint_t sw2_int_cfg = { SW2_IRQ_NUM, SW2_IRQ_PRIORITY };
char msg[] = "Once more unto the breach, dear friends, once more\r\n";

void sw2_int_handler( void )
{
    /* Clear the interrupt - the interrupt is only used to exit deepsleep */
    Cy_GPIO_ClearInterrupt( SW2_PORT, SW2_NUM );
}

int main(void)
{
    /* Initialize the UART */
    Cy_SCB_UART_Init( UART_HW, &UART_config, &UART_context );
    Cy_SCB_UART_Enable( UART_HW );
    
    /* Initialize input pin */
    Cy_GPIO_Pin_FastInit(     SW2_PORT, SW2_NUM, CY_GPIO_DM_PULLUP, 1, HSIOM_SEL_GPIO );
    Cy_GPIO_SetInterruptEdge( SW2_PORT, SW2_NUM, CY_GPIO_INTR_FALLING );
    Cy_GPIO_SetInterruptMask( SW2_PORT, SW2_NUM, 1 );
    Cy_SysInt_Init( &sw2_int_cfg, sw2_int_handler );
    NVIC_EnableIRQ( sw2_int_cfg.intrSrc );
    __enable_irq();
    /* Print the message and allow plenty of time for it to be sent */
    Cy_SCB_UART_PutString( UART_HW, msg );
    Cy_SysLib_Delay( 2000 );
       
    for(;;)
    {
        /* Start printing, wait a while, then go into deepsleep */
        Cy_SCB_UART_PutString( UART_HW, msg );
        Cy_SysLib_Delay( 100 );
        Cy_SysPm_DeepSleep( CY_SYSPM_WAIT_FOR_INTERRUPT );
    }
}

When I run the program the UART prints the first message, because it has plenty of time, but only gets about a quarter of the way through the string after that before sleeping. This is because 100ms is not long enough to complete the printing. When I press SW2 the device exits deepsleep and tries to print again. Note, though, that it re-starts from the beginning of the string.

UART output interrupts by deepsleep transition

The UART "forgets" the contents of its buffer while in deepsleep. I am going to fix that by using the default UART callback. How does that work? The Cy_SysPm_DeepSleep() function looks for, and executes, callbacks that get registered with the Cy_SysPm_RegisterCallback(). When it runs them it passes in a pointer to a structure that controls what actions to perform and what instance of hardware to do them to. Here is my callback parameter struct for the UART. Note that it has three members; an ORed list of conditions, the hardware (SCB address for my UART), and the UART context. The ORed list is asking the callback function (which we do not have to write) to handle four things; check it is OK to go into low power, run code before going to sleep, run code after sleep, and handle any errors.

/* This struct is passed to Cy_SCB_UART_DeepSleepCallback */
cy_stc_syspm_callback_params_t deep_sleep_params = 
{
    CY_SYSPM_CHECK_READY | CY_SYSPM_BEFORE_TRANSITION | CY_SYSPM_AFTER_TRANSITION | CY_SYSPM_FAIL,
    UART_HW,
    &UART_context
};

In order to actually run the callback I create another struct. This contains a pointer to the callback function, the low power state to handle (you can create different callbacks for different states), the conditions to ignore in this particular callback, a pointer to the parameters (above), and a pair of prev and next pointers that I just set to NULL. These last two pointers are set by Cy_SysPm_RegisterCallback() so that it builds a linked list of all callbacks (I only have one).

/* This struct is part of a linked list of callbacks */
cy_stc_syspm_callback_t deep_sleep_checker =
{
    &Cy_SCB_UART_DeepSleepCallback,
    CY_SYSPM_DEEPSLEEP,
    0,
    &deep_sleep_params,
  NULL,
  NULL
};

Next I just register the callback for the UART.

    /* Initialize the UART */
    Cy_SCB_UART_Init( UART_HW, &UART_config, &UART_context );
    Cy_SCB_UART_Enable( UART_HW );
    
    /* Install the UART callback */
    Cy_SysPm_RegisterCallback( &deep_sleep_checker );

Lastly, I re-jig the main loop to check the return value from the call to Cy_SysPM_DeepSleep(), only going into the low power mode once the printing has completed.

        Cy_SCB_UART_PutString( UART_HW, msg );
        do
        {
            Cy_SysLib_Delay( 100 );
        } while( CY_SYSPM_SUCCESS != Cy_SysPm_DeepSleep( CY_SYSPM_WAIT_FOR_INTERRUPT ) );

Now, when I run the program, the whole string is printed every time.

Deepsleep transition delayed to allow the UART buffer to empty

The whole application is attached. I realize that I have glossed over a few areas in this article and so, next time, I'll dig into that a little more and create my own custom callbacks.

 

May 08, 2018

PSoC 6 Low Power MCWDT

[re-printed from iotexpert.com]

In the last article I wrote about using the PSoC 4 Watch Dog Counter as a deep sleep timer.  It seems kind of obvious that I should probably mostly do PSoC 6 articles.  So, in this article, Ill show you how to use the PSoC 6 Low Power MCWDT as a deep sleep timer.  What is a “MCWDT?”, well, the MC stands for Multi-Counter.  And as best I can tell, is exactly the same as the PSoC 4 WDT, except there are two of them in the PSoC 63 instead of the 1 in the PSoC 4.  There is also a dedicated 16-bit WDT (which I will write about in a few days).

Specifically in this article I will show you:

  • The PSoC 6 Low Power MCWDT Multi-Counter WatchDog Timer
  • How to configure the Low Frequency Clock Sources
  • Configuring the Clk_LF Source in Firmware
  • Overall Project Schematic Configuration for the PSoC 6 Low Power MCWDT
  • Configuring the Interrupts
  • Configuring the PSoC 6 Low Power MCWDT
  • The PSoC 6 Low Power MCWDT Firmware

The Multi-Counter WatchDog Timer (MCWDT)

The PSoC 6 Low Power MCWDT is almost exactly the same as the PSoC 4 WDT – except it is in 40nm instead of 130nm.  It has 2x 16-bit counters and 1x 32-bit counter.  The three counters can be cascaded to create long period timers.  Each counter can be configured to clear on match, or free-run.  Each counter can also be configured to cause a device reset if the interrupt is not processed.  The MCWDT works in Active, Low Power Active, Sleep, Low Power Sleep and Deep Sleep power modes.

The design intent is that one MCWDT would be “assigned” to each of the MCUs (i.e. the M4, M0+), but that is not required.  The picture below is a snapshot from the PSoC 63 TRM and explains pretty well how one of the PSoC 6 Low Power MCWDT work.

PSoC 6 Low Power MCWDT

Low Frequency Clock Sources (CLK_LF)

From the picture above you can see that the MCWDT uses the LFLK as the source clock for the counters.  Fortunately or unfortunately there is an inconsistency in the PSoC 63 TRM and PSoC Creator.  The PSoC Creator team decided to unify the names of all of the clocks by calling them all “Clk_*”.  Which is inconsistent with the TRM which calls it “LFLK”.  The LFCLK in the TRM is called the “Clk_LF” in the PSoC Creator GUIs and it is called “CLK_LF” or “ClkLf” in the firmware (confused yet?).  If not then you should be as I was the first time I saw it.

The Clk_LF can be driven by one of three source oscillators.  The PILO, ILO or the WCO.  Look at the upper left hand box where I have pulled down the selection menu.PSoC 6 Clock Configuration

But what is WCO, PILO and ILO?  These are clock sources which you can select the Clk_LF clock source on the “Source Clocks” page.  It can be:

  • The Internal Low Speed Oscillator (ILO) which is a super low power 32KHz RC Oscillator (but not very accurate)
  • The Precision Internal Low Speed Oscillator (PILO) is a more accurate, trim-able RC? oscillator, and presumably higher power (though I don’t know how much).
  • The Watch Crystal Oscillator (WCO) which is a very accurate crystal oscillator that gives you accurate timing at a higher power cost.

With the “Configure System Clocks” window in the DWR you can configure the behavior of the Clock Sources.  Notice that I have turned on all three of the Clock sources (ILO, PILO and WCO) something which you probably wouldn’t actually do.

PSoC 6 Source Clock Configuration

When you click those buttons, PSoC Creator will create a function called “ClockInit” in the file “cyfitter_cfg.c” that is called by the PSoC startup code (and runs before your main() ).  You can see that the firmware enables the PILO and WCO (as well as the ILO which is on by default)

static void ClockInit(void)
{

uint32_t status;
 
/* Enable all source clocks */
Cy_SysClk_PiloEnable();
Cy_SysClk_WcoEnable(900u);
Cy_SysClk_ClkLfSetSource(CY_SYSCLK_CLKLF_IN_PILO);
 
/* Configure CPU clock dividers */
Cy_SysClk_ClkFastSetDivider(0u);
Cy_SysClk_ClkPeriSetDivider(1u);
Cy_SysClk_ClkSlowSetDivider(0u);

Configuring the Clk_LF Source in Firmware

You can also configure the Clk_LF sources in your firmware.  In the firmware below, I check to see if the WCO is running.  If it is, then I set it to be the source of the Clk_LF.  If it is not running, then I try to start it.  And, finally, I print out the current source of the Clk_LF.

    // Is the Watch Crystal Osc running?
    if(Cy_SysClk_WcoOkay())
    {
        printf("Switching ClkLf to WCO\n");
        Cy_SysClk_ClkLfSetSource(CY_SYSCLK_CLKLF_IN_WCO);
    }
    else
    {
        printf("WCO Not functioning attempting a start\n");
        Cy_SysClk_WcoEnable(0); // come back immediately
        for(int i=0;i<100;i++)
        {
            CyDelay(10);
            if(Cy_SysClk_WcoOkay())
            {
                printf("Suceeded in starting WCO in %dms\n",i*10);
                Cy_SysClk_ClkLfSetSource(CY_SYSCLK_CLKLF_IN_WCO);
                break;
            }
        }
        if(!Cy_SysClk_WcoOkay())
        {
            printf("Unable to start WCO in 1000ms\n");
        }
    }
                    
    // What is the clock source of the ClkLf?
    switch(Cy_SysClk_ClkLfGetSource ())
    {
        case CY_SYSCLK_CLKLF_IN_ILO:
            printf("Clk LF = ILO\n");
            break;
        case CY_SYSCLK_CLKLF_IN_ALTLF:
            printf("Clk LF = ALTLF\n");
        break;
        case CY_SYSCLK_CLKLF_IN_PILO:
            printf("Clk LF = PILO\n");
        break;
        case CY_SYSCLK_CLKLF_IN_WCO:
            printf("Clk LF = WCO\n");
        break;
    }

Overall Project Schematic Configuration

To demonstrate the PSoC 6 Low Power MCWDT, I start by creating a schematic.  It has the three color LEDs pins (RED, GREEN and BLUE), a UART to print out debugging information and finally the MCWDT connected to an interrupt.

PSoC 6 Low Power MCWDT Schematic

The pin assignment is chosen to match the pins on my CY8CKIT-062-BLE Development kit. (look on the back)

PSoC 6 Pin Configuration

CY8CKIT-062-BLE

Configuring the Interrupts

When you place an interrupt component in PSoC Creator (which I did in the above schematic), it will then appear in the DWR on the interrupt page.  Here you can assign the interrupt to either of the MCU Cores, or I suppose both, though I think that is  probably a horrible idea.

PSoC Creators “fitter” sees that the interrupt is connected to a MCWDT.  It then does the job of attaching the interrupt to the correct interrupt number, in this case 19

PSoC 6 Interrupts

All the screen above does is create a little block of code in the firmware file cyfitter_sysint_cfg.c.  Look at the structure called cy_stc_sysint_t SysInt_1_cfg in the automatically generated code:

/*******************************************************************************
* File Name: cyfitter_sysint_cfg.c
*
* PSoC Creator  4.2 Nightly Build 543
*
* Description:
*
* This file is automatically generated by PSoC Creator.
*
********************************************************************************
* Copyright (c) 2007-2017 Cypress Semiconductor.  All rights reserved.
* You may use this file only in accordance with the license, terms, conditions,
* disclaimers, and limitations in the end user license agreement accompanying
* the software package with which this file was provided.
********************************************************************************/
 
#include "cyfitter_sysint.h"
#include "cyfitter_sysint_cfg.h"
 
/* ARM CM4 */
#if (((__CORTEX_M == 4) && (CY_CORE_ID == 0)))
 
    /* SysInt_1 */
    const cy_stc_sysint_t SysInt_1_cfg = {
        .intrSrc = (IRQn_Type)SysInt_1__INTC_NUMBER,
        .intrPriority = SysInt_1__INTC_CORTEXM4_PRIORITY
    };
 
    /* UART_SCB_IRQ */
    const cy_stc_sysint_t UART_SCB_IRQ_cfg = {
        .intrSrc = (IRQn_Type)UART_SCB_IRQ__INTC_NUMBER,
        .intrPriority = UART_SCB_IRQ__INTC_CORTEXM4_PRIORITY
    };
 
#endif /* ((__CORTEX_M == 4) && (CY_CORE_ID == 0)) */

So, where does SysInt_1__INTC_NUMBER get set?  That is done by the fitter in cyfitter_sysint.h.  Here is the automatically generated code:

/*******************************************************************************
* File Name: cyfitter_sysint.h
*
* PSoC Creator  4.2 Nightly Build 543
*
* Description:
*
* This file is automatically generated by PSoC Creator.
*
********************************************************************************
* Copyright (c) 2007-2017 Cypress Semiconductor.  All rights reserved.
* You may use this file only in accordance with the license, terms, conditions,
* disclaimers, and limitations in the end user license agreement accompanying
* the software package with which this file was provided.
********************************************************************************/
 
#ifndef INCLUDED_CYFITTER_SYSINT_H
#define INCLUDED_CYFITTER_SYSINT_H
#include "cy_device_headers.h"
 
/* SysInt_1 */
#define SysInt_1__INTC_CORTEXM4_ASSIGNED 1
#define SysInt_1__INTC_CORTEXM4_PRIORITY 7u
#define SysInt_1__INTC_NUMBER 19u
#define SysInt_1_INTC_CORTEXM4_ASSIGNED 1
#define SysInt_1_INTC_CORTEXM4_PRIORITY 7u
#define SysInt_1_INTC_NUMBER 19u
 
/* UART_SCB_IRQ */
#define UART_SCB_IRQ__INTC_CORTEXM4_ASSIGNED 1
#define UART_SCB_IRQ__INTC_CORTEXM4_PRIORITY 7u
#define UART_SCB_IRQ__INTC_NUMBER 46u
#define UART_SCB_IRQ_INTC_CORTEXM4_ASSIGNED 1
#define UART_SCB_IRQ_INTC_CORTEXM4_PRIORITY 7u
#define UART_SCB_IRQ_INTC_NUMBER 46u
 
#endif /* INCLUDED_CYFITTER_SYSINT_H */

To make all of this work, you simply need to install the interrupt handler by calling Cy_SysInt_Init in your main.c.

    // install ISR...
    Cy_SysInt_Init(&SysInt_1_cfg,myWDT);
    NVIC_EnableIRQ(SysInt_1_INTC_NUMBER);

Configuring the PSoC 6 Low Power MCWDT with the GUI

As with all (or almost all) PSoC components, you can configure them in the GUI.  In the picture below you can see that you can set

  1. Enable/Disable
  2. The Match Value
  3. The Mode (interrupt, watchdog, none)
  4. Free Running or Clear on Match
  5. The Cascade (meaning connect the counters together)

PSoC 6 Low Power MCWDT Configuration GUI

All of those configuration items will end up in the file MCWDT_1_PDL.c in a structure that looks like this:

/** The instance-specific configuration structure. This should be used in the
*  associated MCWDT_1_Init() function.
*/
const cy_stc_mcwdt_config_t MCWDT_1_config =
{
    .c0Match     = MCWDT_1_C0_MATCH,
    .c1Match     = MCWDT_1_C1_MATCH,
    .c0Mode      = MCWDT_1_C0_MODE,
    .c1Mode      = MCWDT_1_C1_MODE,
    .c2ToggleBit = MCWDT_1_C2_PERIOD,
    .c2Mode      = MCWDT_1_C2_MODE,
    .c0ClearOnMatch = (bool)MCWDT_1_C0_CLEAR_ON_MATCH,
    .c1ClearOnMatch = (bool)MCWDT_1_C1_CLEAR_ON_MATCH,
    .c0c1Cascade = (bool)MCWDT_1_CASCADE_C0C1,
    .c1c2Cascade = (bool)MCWDT_1_CASCADE_C1C2
};

Then you can either call the MCWDT_1_Start() function or you can call the PDL function Cy_MCWDT_Init(&MCWDT_1_config);

PSoC 6 Low Power MCWDT Firmware

You can also configure the PSoC 6 Low Power MCWDT in your firmware as I have done below:

   Cy_MCWDT_Unlock(MCWDT_STRUCT0);
    // Turn off all of the counters
    Cy_MCWDT_Disable(MCWDT_STRUCT0,CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2,100);
    Cy_MCWDT_ResetCounters(MCWDT_STRUCT0,CY_MCWDT_CTR0,100);
    Cy_MCWDT_ResetCounters(MCWDT_STRUCT0,CY_MCWDT_CTR1,100);
    
    Cy_MCWDT_SetMode(MCWDT_STRUCT0,0,CY_MCWDT_MODE_NONE); // 0=Counter 0
    Cy_MCWDT_SetMode(MCWDT_STRUCT0,1,CY_MCWDT_MODE_INT);  // 1=Counter 1
    
    Cy_MCWDT_SetCascade(MCWDT_STRUCT0,CY_MCWDT_CASCADE_C0C1 );
    
    Cy_MCWDT_SetInterruptMask(MCWDT_STRUCT0,CY_MCWDT_CTR1); // Only take ints from counter 1
    
    // delay = 32768*16 = 8 seconds & 32khz
    Cy_MCWDT_SetMatch(MCWDT_STRUCT0,0,32768,100);
    Cy_MCWDT_SetMatch(MCWDT_STRUCT0,1,16,100);
      
    Cy_MCWDT_SetClearOnMatch(MCWDT_STRUCT0,0,1);
    Cy_MCWDT_SetClearOnMatch(MCWDT_STRUCT0,1,1);
    
    Cy_MCWDT_Enable(MCWDT_STRUCT0,CY_MCWDT_CTR0|CY_MCWDT_CTR1,100);

The Interrupt Service Routine (ISR) for the MCWDT is pretty simple.  It just reads the cause of the interrupt, which could be any one of the counters in the MCWDT (or more than one of them).  Then it inverts either the Red, Green or Blue LED.  And finally it clears the interrupt source inside of the MCWDT.

void myWDT(void)
{
    uint32_t cause;
    cause = Cy_MCWDT_GetInterruptStatusMasked(MCWDT_STRUCT0);
    if(cause & CY_MCWDT_CTR0)
    {
        Cy_GPIO_Inv(RED_PORT,RED_NUM);
    }
    
    if(cause & CY_MCWDT_CTR1)
    {
        Cy_GPIO_Inv(GREEN_PORT,GREEN_NUM);
    }
    
    if(cause & CY_MCWDT_CTR2)
    {
        Cy_GPIO_Inv(BLUE_PORT,BLUE_NUM);
    }
    
    Cy_MCWDT_ClearInterrupt(MCWDT_STRUCT0,cause);
}

When I run this program I get a slowly blinking Green LED in Low Power mode:

PSoC 6 Low Power MCWDT

 

Apr 19, 2018

More PDL Examples - Port Interrupts

In my last post I covered input pins and compared the component and firmware-only approaches. Now I shall do the same with interrupts. If you remember, I have a very simple design with just a couple of pins - SW2 (input) and LED9 (output). 

Input and output pins in PSoC Creator schematic

To start the interrupt discussion I open the customizer for the SW2 GPIO component,  enable interrupts and select a falling edge condition. The falling edge means I will get an interrupt when I press, rather than release, the button.

Selecting Falling Edge Interrupts in the PSoC GPIO_PDL Component

Notice that, unlike with PSoC 4 projects, the component instance does not expose an interrupt terminal. This is because pins generate interrupts on a port and so multiple pins may need to share an interrupt. For example, in last week's project I put two switches on port 0 and so, if I configured both to generate interrupts, they would share just one actual IRQ with the wonderful name "ioss_interrupts_gpio_0_IRQn". PSoC Creator does not support wiring multiple interrupt terminals to one interrupt component and so we created a Global Signal Reference component that lets you pick an IRQ and attach an interrupt component.
IMG Configuring IRQ0 in PSoC Creator - two images

Configuring IRQ0 in PSoC Creator

Configuring IRQ0 in PSoC Creator

One thing to remember here is that if you re-map the pin to another port via the resources file then make sure you re-select the port IRQ to match the new one or your interrupts will stop firing.

In firmware I need to install and enable the interrupt handler and remove all the code from the main loop. Notice how I use the generated SW2_Int_cfg struct in the function arguments. That is the initialization struct generated by the SW2_Int component. I'll talk about this a little more in a moment!

    Cy_SysInt_Init( &SW2_Int_cfg, button_handler );
    NVIC_EnableIRQ( SW2_Int_cfg.intrSrc );

In my handler I just toggle the pin so that it turns on and off when I press the switch. Here is all the code.

void button_handler( void )
{
    Cy_GPIO_ClearInterrupt( SW2_PORT, SW2_NUM );
    Cy_GPIO_Write( LED9_PORT, LED9_NUM, ! Cy_GPIO_ReadOut( LED9_PORT, LED9_NUM ) );
}

int main(void)
{    
    Cy_SysInt_Init( &SW2_Int_cfg, button_handler );
    NVIC_EnableIRQ( SW2_Int_cfg.intrSrc );
    
    __enable_irq();
    
    for(;;)
    {   
    }
}

Now let's re-write that for the firmware-only solution. It is mostly the same except I need to set the interrupt condition to falling edge in firmware and tell the port that I want to interrupt on this pin (bit). Here is the code to replicate the program above for the manual (jumper wire) SW2 switch and LED8.

#define LED8_PORT   GPIO_PRT1  
#define LED8_NUM    5
#define SW_PORT     GPIO_PRT0
#define SW_NUM      2

void button_handler( void )
{
    Cy_GPIO_ClearInterrupt( SW_PORT, SW_NUM );
    Cy_GPIO_Write( LED8_PORT, LED8_NUM, !Cy_GPIO_ReadOut( LED8_PORT, LED8_NUM ) );
}
int main(void)
{    
    Cy_GPIO_Pin_FastInit( LED8_PORT, LED8_NUM, CY_GPIO_DM_STRONG, 1, HSIOM_SEL_GPIO );
    Cy_GPIO_Pin_FastInit( SW_PORT, SW_NUM, CY_GPIO_DM_PULLUP, 1, HSIOM_SEL_GPIO );
    Cy_GPIO_SetInterruptEdge( SW_PORT, SW_NUM, CY_GPIO_INTR_FALLING );
    Cy_GPIO_SetInterruptMask( SW_PORT, SW_NUM, 1 );
    
    cy_stc_sysint_t SW_Int_cfg = { ioss_interrupts_gpio_0_IRQn, 3 };
    Cy_SysInt_Init( &SW_Int_cfg, button_handler );
    NVIC_EnableIRQ( SW_Int_cfg.intrSrc );
    
    __enable_irq();
    
    for(;;)
    {       
    }
}

Notice that I have to create my own cy_stc_sysint_t struct for the call to Cy_SysInt_Init(). It's not a hardship - just two members - the port number and interrupt priority. I'll finish the article by merging the two solutions - using one port IRQ to toggle two LEDs with two switches (I deliberately put my jumper wire switch on port 0 to make this point). All I have to do is add some code to the handler to recognize the generating pin. PDL gives us the Cy_GPIO_GetInterruptStatus() function to do just that.

void button_handler( void )

    if( Cy_GPIO_GetInterruptStatus( SW_PORT, SW_NUM ) == CY_GPIO_INTR_STATUS_MASK )
    {
        Cy_GPIO_ClearInterrupt( SW_PORT, SW_NUM );
        Cy_GPIO_Write( LED8_PORT, LED8_NUM, !Cy_GPIO_ReadOut( LED8_PORT, LED8_NUM ) );
    }
    else if( Cy_GPIO_GetInterruptStatus( SW2_PORT, SW2_NUM ) == CY_GPIO_INTR_STATUS_MASK )
    {
        Cy_GPIO_ClearInterrupt( SW2_PORT, SW2_NUM );
        Cy_GPIO_Write( LED9_PORT, LED9_NUM, !Cy_GPIO_ReadOut( LED9_PORT, LED9_NUM ) );
    }
}

I made no changes to the body of the main() program because it already sets up the SW switch and installs the ISR into the vector table. My program reliably toggles the LEDs (although I have to admit that my jumper wire switch is far from clean and tends to generate many interrupts whenever I poke the wire into the J4 header).

As you can see, it is perfectly simple to use interrupts with the schematic file or firmware-only, or even with a mixture of the two. There is no performance advantage in either approach and so which one to use is entirely up to you. I tend to like the firmware approach because I can read the pin drive mode, physical address (e.g.P0.2), interrupt condition, priority level and enablement, all in my C code. Some of my colleagues, the other hand, are very thorough at documenting their hardware setup in the schematic file, with multiple pages and plenty of text boxes, so they prefer the schematic approach. Who is right? Well, its me, obviously, but that may depend on who you ask!

 

Apr 05, 2018

More PDL Examples - Input Pins

Today I shall give you some pointers for using GPIO as inputs. As with outputs you can configure them with the schematic file or by writing firmware. Here is an instance of the GPIO_PDL component in a schematic.

Input pin in PSoC Creator schematic (with LED9 output)

I configured the component to be called "SW2" because I am going to map it to the pin that connects to that switch on the Pioneer kit. In the customizer I also disabled the hardware connection, so I can use it exclusively in firmware, and set the drive mode to be resistive pull up. This means that the pin reads high when the switch is not pressed and goes low when it is pressed (active-low) and grounds the pin.

Pin configuration inside PSoC Creator GPIO customizer

In the resources file I chose pin P0.4 and you can see that reflected inside the schematic pin symbol (above).

When I build this, PSoC Creator does the magic and I can start reading the pin in main() with no initialization code. I kept the LED9 component from the last week's article and this code makes it follow the state of the switch.

    for(;;)
    {
        Cy_GPIO_Write( LED9_PORT, LED9_NUM, Cy_GPIO_Read( SW2_PORT, SW2_NUM ) ); 
    }

OK, that was easy. How do I do it using just PDL firmware? Well, it just takes one extra line of code. Now, I do not have another switch on the kit so I chose to use a jumper wire as a really, really, really naughty alternative - connecting P0.2 in header J4 to ground manually. Note how I initialize the pin and changed the code in the loop to use my new "switch".

    Cy_GPIO_Pin_FastInit( GPIO_PRT0, 2, CY_GPIO_DM_PULLUP, 1, HSIOM_SEL_GPIO );

    for(;;)
    {
        Cy_GPIO_Write( LED9_PORT, LED9_NUM, Cy_GPIO_Read( GPIO_PRT0, 2 ) ); 
    }

So this works great but it contains those scary "magic numbers" that will bite me if I try to do this is a real (bigger) application. Let me fix that right away, before I get a call from the Cypress code quality police.

#define SW_PORT     GPIO_PRT0
#define SW_NUM      2

    Cy_GPIO_Pin_FastInit( SW_PORT, SW_NUM, CY_GPIO_DM_PULLUP, 1, HSIOM_SEL_GPIO );

    for(;;)
    {
        Cy_GPIO_Write( LED9_PORT, LED9_NUM, Cy_GPIO_Read( SW_PORT, SW_NUM ) ); 
    }

I may only have only one real switch (I called my jumper wire "SW") but I do have another LED. It's LED8 on P1.5. Here is a program that drives LED9 from SW2 and LED8 from SW (naughty switch) to further contrast the approaches.

#define SW_PORT     GPIO_PRT0
#define SW_NUM      2

#define LED8_PORT   GPIO_PRT1  
#define LED8_NUM    5

    Cy_GPIO_Pin_FastInit( SW_PORT, SW_NUM, CY_GPIO_DM_PULLUP, 1, HSIOM_SEL_GPIO );
    Cy_GPIO_Pin_FastInit( LED8_PORT, LED8_NUM, CY_GPIO_DM_STRONG, 1, HSIOM_SEL_GPIO );

    for(;;)
    {
        Cy_GPIO_Write( LED9_PORT, LED9_NUM, Cy_GPIO_Read( SW2_PORT, SW2_NUM ) ); 
        Cy_GPIO_Write( LED8_PORT, LED8_NUM, Cy_GPIO_Read( SW_PORT, SW_NUM ) );
    }

So, which of these methods should you use? Well, it's 100% up to you because neither is really better than the other. If you use a component in a schematic then PSoC Creator will stop you from inadvertently using that pin for some other function. That's quite nice but most of the time that just does not happen because P0.4 is connected to SW2 and there is no reason to try to use it for anything else. With the firmware-only approach you need to remember to initialize the pin but it is much fast to make a change to another pin and re-build.

Next time I shall beef this up a little by making the program interrupt-driven and I'll explain how to manage multiple interrupts from one GPIO port.

 

Mar 30, 2018

More PDL Examples - Wiggling Pins

Let's see how many ways we can wiggle a pin! OK, there are too many, so let's limit the discussion to firmware-driven pins and just mess about with some more PDL functions instead! I'll point out a few tips and tricks along the way.

I'll start with the humble output pin and I'll wiggle pin 7 on port 13 because it is connected to LED9 (the red one) on the Pioneer kit. The full-featured, no-holds-barred, control-freaks-and-experts-only method is to use the Cy_GPIO_Pin_Init() function. It looks innocent enough, with just three arguments for the port, pin number, and a pointer to a configuration struct.

cy_en_gpio_status_t Cy_GPIO_Pin_Init(
    GPIO_PRT_Type * base,
    uint32_t pinNum,
    const cy_stc_gpio_pin_config_t * config );

The first two are just telling the function which pin to initialize. But that configuration guy is a bit mean!

typedef struct {
    uint32_t outVal;         /**< Pin output state */
    uint32_t driveMode;      /**< Drive mode */
    en_hsiom_sel_t hsiom;    /**< HSIOM selection */
    uint32_t intEdge;        /**< Interrupt Edge type */
    uint32_t intMask;        /**< Interrupt enable mask */
    uint32_t vtrip;          /**< Input buffer voltage trip type */
    uint32_t slewRate;       /**< Output buffer slew rate */
    uint32_t driveSel;       /**< Drive strength */
    uint32_t vregEn;         /**< SIO pair output buffer mode */
    uint32_t ibufMode;       /**< SIO pair input buffer mode */
    uint32_t vtripSel;       /**< SIO pair input buffer trip point */
    uint32_t vrefSel;        /**< SIO pair reference voltage for input buffer trip point */
    uint32_t vohSel;         /**< SIO pair regulated voltage output level */
} cy_stc_gpio_pin_config_t;

OK! Breathe! Don't be scared! I told you this was the expert method! Actually it is easier than it looks and this program should illustrate that.

    cy_stc_gpio_pin_config_t pinConfig =
    {
        .outVal =       1UL,                    
        .driveMode =    CY_GPIO_DM_STRONG,      
        .hsiom =        0,                      
        .intEdge =      CY_GPIO_INTR_DISABLE,
        .intMask =      0UL,
        .vtrip =        CY_GPIO_VTRIP_CMOS,
        .slewRate =     CY_GPIO_SLEW_FAST,
        .driveSel =     CY_GPIO_DRIVE_FULL,
        .vregEn =       0UL,                 
        .ibufMode =     0UL,                   
        .vtripSel =     0UL,                 
        .vrefSel =      0UL,                   
        .vohSel =       0UL               
    };
    
    Cy_GPIO_Pin_Init( P13_7_PORT, P13_7_NUM, &pinConfig );
    for(;;)
    {
        Cy_GPIO_Write( P13_7_PORT, P13_7_NUM, ! Cy_GPIO_Read( P13_7_PORT, P13_7_NUM ) );
        Cy_SysLib_Delay( 100 );
    }

Walking through the code, I initialize the pinConfig struct as follows.

  • outVal is the initial state of the pin (what do I want it to be after initialization and before the wiggling starts)
  • driveMode determines whether it is an input or output and whatr to do when it is coonnected to external hardware (strong means it will control the external LED)
  • hsiom is the internal connection, which I am not using because this is a firmware-controlled pin
  • intEdge is the interrupt type for an input pin
  • intMask enables the port interrupt for this pin
  • vtrip determines the voltage at which an input pin changes state
  • slewRate is the speed at which the pin responds to a change - either fast or slow
  • driveSel determines the strength to drive the pin

Most of that is pretty obvious really. After driveSel there are a bunch of SIO-specific options that do not apply to simple GPIOs.

So, I have initialized my pin and then I wiggle it by reading its state, inverting that, writing it back to the pin and then waiting for 100 milliseconds before repeating the process. Blinky! Now, before you despair of all the code you are going to need for your pins, let's make this easier. There is a very friendly alternative to the Cy_GPIO_Pin_Init() function called Cy_GPIO_Pin_FastInit(). This guy hides all the difficult stuff that most of us never use and just asks you for the drive mode and the connection. Here is my simplified blinky.

    Cy_GPIO_Pin_FastInit( P13_7_PORT, P13_7_NUM, CY_GPIO_DM_STRONG, 1, HSIOM_SEL_GPIO );
    for(;;)
    {
        Cy_GPIO_Write( P13_7_PORT, P13_7_NUM, ! Cy_GPIO_Read( P13_7_PORT, P13_7_NUM ) );
        Cy_SysLib_Delay( 100 );
    }

All that configuration data has gone away, which I like, but I still have that ugly read-modify-write code with Cy_GPIO_Read() and Cy_GPIO_Write(). PSoC 6 pins have a handy "invert" bit and the PDL gives us a handy Cy_GPIO_Inv() function that writes to it and makes my program even simpler.

    Cy_GPIO_Pin_FastInit( P13_7_PORT, P13_7_NUM, CY_GPIO_DM_STRONG, 1, HSIOM_SEL_GPIO );
    for(;;)
    {
        Cy_GPIO_Inv( P13_7_PORT, P13_7_NUM );
        Cy_SysLib_Delay( 100 );
    }

I think this is a really compact program but I can make it even simpler and more maintainable (is that a real word?). You may have noticed that I have not used the schematic file yet. My next step is to use a pin component (GPIO_PDL) and lock it to P13.7 in the resources file.
 

PSoC Output Pin Customizer

PSoC Creator Pin Selection

By doing this, PSoC Creator initializes the pin for me and so my FastInit call goes away. It also creates macros for the port and pin, so my new program looks like this.

    for(;;)
    {
        Cy_GPIO_Inv( LED9_PORT, LED9_NUM );
        Cy_SysLib_Delay( 100 );
    }

There is another reason for working this way - maintainability (yeah, it's a word!). In the first three examples I write directly to the pin registers from firmware. That's fine but if I ever want to wiggle a different pin I need to edit all the code. I can make that better by defining a couple of macros (e.g. #define LED9_PORT P13_7_PORT) and then I can change the pin with a simple edit and rebuild. That works really well but you need to remember all the pins you are accessing in firmware this way and be sure not to map another function to them in the PSoC Creator resource file. Your results will not be super if you try to write to a pin that is being driven from a PWM!!!

Before I go, there is one more function I want to tell you about - Cy_GPIO_ReadOut(). It is a lot like Cy_GPIO_Read() except it reads the state of output driver instead of the input buffer. This is important because the input buffer only refelects the state of the output if the pin is configured to be both and input and an output.
 

PSoC Creator Customizer Output and Input


If I do not check that extra box then Cy_GPIO_Read() will keep reading the same value, and my LED will not blink, whereas Cy_GPIO_ReadOut() always behaves nicely. Just for fun, here is one last version of blinky.

    for(;;)
    {
        Cy_GPIO_Write( LED9_PORT, LED9_NUM, ! Cy_GPIO_ReadOut( LED9_PORT, LED9_NUM ) );
        Cy_SysLib_Delay( 100 );
    }

At the start of this blog I intended to cover both output and input GPIO. But I think I've filled your heads up, so I'll stop now and will write about inputs next week.
 

ALL CONTENT AND MATERIALS ON THIS SITE ARE PROVIDED "AS IS". CYPRESS SEMICONDUCTOR AND ITS RESPECTIVE SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY OF THESE MATERIALS FOR ANY PURPOSE AND DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THESE MATERIALS, INCLUDING BUT NOT LIMITED TO, ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT OF ANY THIRD PARTY INTELLECTUAL PROPERTY RIGHT. NO LICENSE, EITHER EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, IS GRANTED BY CYPRESS SEMICONDUCTOR. USE OF THE INFORMATION ON THIS SITE MAY REQUIRE A LICENSE FROM A THIRD PARTY, OR A LICENSE FROM CYPRESS SEMICONDUCTOR.

Content on this site may contain or be subject to specific guidelines or limitations on use. All postings and use of the content on this site are subject to the Terms and Conditions of the site; third parties using this content agree to abide by any limitations or guidelines and to comply with the Terms and Conditions of this site. Cypress Semiconductor and its suppliers reserve the right to make corrections, deletions, modifications, enhancements, improvements and other changes to the content and materials, its products, programs and services at any time or to move or discontinue any content, products, programs, or services without notice.