You are here

PDL Examples - Custom Low Power Callbacks (SysPm) | Cypress Semiconductor

PDL Examples - Custom Low Power Callbacks (SysPm)

In PDL Examples - SysPM Deepsleep Callbacks I wrote about using the PDL default callback function to make sure my UART could finish sending characters before the device went into deepsleep mode. In that blog I glossed over the conditions that can be handled by the callback mechanism. I'll explain those now by creating and using a (simple) custom callback handler.

My callback handler is going to just report the event it is handling (its not really a useful handler but it does explain how this stuff works) so I first made a quick change to the project. I changed the UART baud rate to 115200 so that I do not have to wait ages for the strings to get printed. Then I wrote my handler function which I intend to use in a pair of callbacks. Here is the code.

cy_en_syspm_status_t custom_sleep_handler( cy_stc_syspm_callback_params_t *callbackParams )
{
    cy_en_syspm_status_t retval = CY_SYSPM_SUCCESS;
    
    switch( callbackParams->mode )
    {
        case CY_SYSPM_CHECK_READY:
            Cy_SCB_UART_PutString( UART_HW, "CY_SYSPM_CHECK_READY\t" );
            break;
        case CY_SYSPM_CHECK_FAIL:
            Cy_SCB_UART_PutString( UART_HW, "CY_SYSPM_CHECK_FAIL\t" );
        break;
        case CY_SYSPM_BEFORE_TRANSITION:
            Cy_SCB_UART_PutString( UART_HW, "CY_SYSPM_BEFORE_TRANSITION\t" );
            Cy_SysLib_Delay( 10 );
        break;
        case CY_SYSPM_AFTER_TRANSITION:
            Cy_SCB_UART_PutString( UART_HW, "CY_SYSPM_AFTER_TRANSITION\t" );
        break;
        default:
            Cy_SCB_UART_PutString( UART_HW, "Error\t\t" );
        break;
    }
    
    Cy_SCB_UART_PutString( UART_HW, (char *)callbackParams->context );
    return retval;
}

What is going on here? It's a function that gets called (once I register it) when the device goes into low power mode. There are four situations that are handled when that occurs.

CY_SYSPM_CHECK_READY
This is the first thing that the SysPM driver does. It is a check that the application is ready to go to sleep. It can check anything it likes but, in my (rather silly) example it just prints out the state ("CY_SYSPM_CHECK_READY") and the context (more later), then returns success.

CY_SYSPM_CHECK_FAIL
This gets called if the check fails. In my program this cannot happen because the CHECK_READY always returns success. I'll mess with this later on!

CY_SYSPM_BEFORE_TRANSITION
This is where you would do final housekeeping before the device goes into low power - things like closing down the peripheral nicely. It does not get called if the CHECK_READY returns failure. Notice that I put a delay call in the code to ensure that the UART has time to print before going into low power.

CY_SYSPM_AFTER_TRANSITION
When the device exits low power (after an interrupt) this is where you would re-start the peripheral prior to returning to the application code.

So my handler simply prints out the callbackParams->mode and the callbackParams->context. What's that about? Normally you write these callbacks for a specific peripheral and you pass in that peripheral's context struct. That's what I did for the UART last time.

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
};

This time, however, I am not actually touching the peripherals and I pass in a string. This is going to let me create two callbacks and the handler will tell me which one is running. Here is my code to create two sets of callback parameters with the strings.

char cb1_msg[] = "ONE\r\n";
char cb2_msg[] = "TWO\r\n";
cy_stc_syspm_callback_params_t sleep_params1 = 
{
    CY_SYSPM_CHECK_READY | CY_SYSPM_BEFORE_TRANSITION | CY_SYSPM_AFTER_TRANSITION | CY_SYSPM_FAIL,
    NULL,
    (void *)&cb1_msg
};
cy_stc_syspm_callback_params_t sleep_params2 = 
{
    CY_SYSPM_CHECK_READY | CY_SYSPM_BEFORE_TRANSITION | CY_SYSPM_AFTER_TRANSITION | CY_SYSPM_FAIL,
    NULL,
    (void *)&cb2_msg
};

Note that I set them both up to handle all four situations, passed a NULL as the peripheral (because I am not using that in my custom handler), and the string to print as the context. These parameters are then included in the callback definitions and registered in main().

cy_stc_syspm_callback_t sleep_checker1 =
{
    custom_sleep_handler,
    CY_SYSPM_SLEEP,
    0,
    &sleep_params1,
    NULL,
    NULL
};
cy_stc_syspm_callback_t sleep_checker2 =
{
    custom_sleep_handler,
    CY_SYSPM_SLEEP,
    0,
    &sleep_params2,
    NULL,
    NULL
};

...

    /* Install my callbacks */
    Cy_SysPm_RegisterCallback( &sleep_checker1 );
    Cy_SysPm_RegisterCallback( &sleep_checker2 );
    Cy_SCB_UART_PutString( UART_HW, "Reset\r\n" );
    Cy_SysLib_Delay( 10 );
       
    for(;;)
    {        
        /* Go to sleep and let the callback explain what is happening */
        Cy_SysPm_Sleep( CY_SYSPM_WAIT_FOR_INTERRUPT );
    }

Both of my callbacks use the same handler function, which handles the sleep low power mode (last time I used deepsleep but they both work the same way), but take different context values.

Here's what happens when I run the program.

PSoC going to sleep successfully

Here you can see that I have reset the board, the program asks to go to sleep and the callbacks are run. First it checks both "peripherals" are ready. They are so it then executes the BEFORE code and goes to sleep. Then I press the switch to cause an interrupt and wake up the part.

PSoC waking up successfully

It runs the AFTER code for both callbacks. Note that it does this in reverse order so that the peripheral that goes to sleep last wakes up first. I can repeat this sequence as often as I like by pressing the switch.
So this shows how to set up the handler for three of the cases. It's pretty simple but powerful. The next step is to show you how to handle cases where the device cannot go to sleep after all - the FAIL case. It's junky code but I created a "fake_error" global and I toggle it in main().

    for(;;)
    {
        Cy_SCB_UART_PutString( UART_HW, "Error is " );
        Cy_SCB_UART_PutString( UART_HW, fake_error ? "TRUE\r\n" : "FALSE\r\n" );
        
        /* Go to sleep and let the callback explain what is happening */
        Cy_SysPm_Sleep( CY_SYSPM_WAIT_FOR_INTERRUPT );
        
        /* Now go to sleep again but toggle a fake error condition */
        fake_error =  !fake_error;
    }

In the callback switch block I modify the CHECK case to look for the error on the second callback and return a FAIL condition.

        case CY_SYSPM_CHECK_READY:
            Cy_SCB_UART_PutString( UART_HW, "CY_SYSPM_CHECK_READY\t" );
            if( fake_error && ! strncmp( (const char *)"TWO", (const char *)callbackParams->context, 3 ) )
                retval = CY_SYSPM_FAIL;
            break;

When I run this code it starts the same way (with an extra message about the error state).

PSoC going to sleep with no error condition

When I press the switch, though, the behavior changes. It runs the CHECK conditions, as before, but because the second one fails, it runs FAIL code and does not go to sleep at all. Note, though, that it only runs the FAIL code for the first callback, not the one that actually failed. It is giving the other callbacks a chance to "back out" any changes that they made on the assumption that they were going to sleep.

PSoC refusing to go to sleep due to an error condition

Attached is the whole project, which includes all the UART, switch pin and interrupt setup code that I reused from the previous blog. I hope it all makes sense. There a few concepts to figure out but I found that, by writing this application and playing around with different choices, I quickly got the hang of things. If you need to build a complex but robust sleep or deepsleep transition please take the time to replicate my project - I think it will be worth your effort!
 

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.