element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Members
    Members
    • Benefits of Membership
    • Achievement Levels
    • Members Area
    • Personal Blogs
    • Feedback and Support
    • What's New on element14
  • Learn
    Learn
    • Learning Center
    • eBooks
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Dev Tools
    • Manufacturers
    • Raspberry Pi
    • RoadTests & Reviews
    • Avnet Boards Community
    • Product Groups
  • Store
    Store
    • Visit Your Store
    • Choose Another Store
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
Embedded
  • Technologies
  • More
Embedded
Blog PSoC 6 and ModusToolbox: UART receiver with FreeRTOS
  • Blog
  • Forum
  • Documents
  • Events
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Embedded requires membership for participation - click to join
Blog Post Actions
  • Subscribe by email
  • More
  • Cancel
  • Share
  • Subscribe by email
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 13 Mar 2021 10:26 PM Date Created
  • Views 3039 views
  • Likes 10 likes
  • Comments 21 comments
  • infineon
  • rtos
  • psoc 6
  • psoc6
  • freertos
  • cypress
Related
Recommended

PSoC 6 and ModusToolbox: UART receiver with FreeRTOS

Jan Cumps
Jan Cumps
13 Mar 2021

The steps to integrate incoming UART data with FreeRTOS on a PSoC 6.

The focus is on saving processor power. The design does not poll for incoming data.

It yields all powers to the RTOS scheduler (possibly going to low power mode) until a trigger fires after a defined number of bites arrived at the UART input.

image

 

Define a UART with TRIGGER Support

 

In the initial part of the exercise, I follow the PSoC 6 documentation for Serial Communication Block configuration.

You will recognise 90% of it. The only changes I made to their example is:

  • Deal with a board that has a 100 MHz peripheral clock instead of a 50 MHz one.
  • Make a 9600 baud UART instead of a 115200 one.
  • every hardware allocation is a define, so that it's easy to switch between all available UART ports,  pins and clock dividers.
  • integrate with FreeRTOS

 

The PSoC 6 documentation is good and linked above, so I will not repeat any of that. Just show how I changed it.

The initialisation code is fully RTOS agnostic. But before going to it, let's check the constants I defined to make it easy to change ports and speed:

 

/* dependent on the SCB and pins used */
#define UART_SCB SCB5
#define UART_PORT       P5_0_PORT
#define UART_RX_NUM     P5_0_NUM
#define UART_TX_NUM     P5_1_NUM
#define UART_RX_PIN     P5_0_SCB5_UART_RX
#define UART_TX_PIN     P5_1_SCB5_UART_TX
#define UART_DIVIDER_NUMBER 5U

#define UART_CLOCK PCLK_SCB5_CLOCK

  /* Assign UART interrupt number and priority */
#define UART_INTR_NUM        ((IRQn_Type) scb_5_interrupt_IRQn)
#define UART_INTR_PRIORITY   (7U)

/* dependent on the BAUD used */
// jc 20210313: 9600 baud needs a 16 bit divider
/* UART desired baud rate is 9600 bps
 * The UART baud rate = (clk_scb / Oversample).
 * For clk_peri = 100 MHz, select divider value 864 and get SCB clock = (100 MHz / 864) = 115.7 kHz.
 * Select Oversample = 12. These setting results UART data rate = 115.7 kHz / 12 = 9645 bps.
 */

#define UART_CLK_DIV_TYPE     (CY_SYSCLK_DIV_16_BIT)
#define UART_CLK_DIV_NUMBER   (UART_DIVIDER_NUMBER)
#define UART_OVERSAMPLE 12UL
#define UART_DIVISION 863UL

/* application dependent UART settings */
#define UART_BUFFER_SIZE 16

 

Now the init code. I call it in my main(), after the board configuration and before the interrupts are activated:

  /* Initialize the device and board peripherals */
  result = cybsp_init() ;
  if (result != CY_RSLT_SUCCESS)
  {
    CY_ASSERT(0);
  }
  initUART();
  __enable_irq();

 

Here is the code. It uses SCB 5 (the debug port) at 9600 baud. With interrupt enable.

 

/* Allocate context for UART operation */
cy_stc_scb_uart_context_t uartContext;

void initUART() {
  // https://cypresssemiconductorco.github.io/psoc6pdl/pdl_api_reference_manual/html/group__group__scb__uart.html


  /* Populate configuration structure */
  const cy_stc_scb_uart_config_t uartConfig = {
      .uartMode                   = CY_SCB_UART_STANDARD,
      .enableMutliProcessorMode   = false,
      .smartCardRetryOnNack       = false,
      .irdaInvertRx               = false,
      .irdaEnableLowPowerReceiver = false,
      .oversample                 = UART_OVERSAMPLE,
      .enableMsbFirst             = false,
      .dataWidth                  = 8UL,
      .parity                     = CY_SCB_UART_PARITY_NONE,
      .stopBits                   = CY_SCB_UART_STOP_BITS_1,
      .enableInputFilter          = false,
      .breakWidth                 = 11UL,
      .dropOnFrameError           = false,
      .dropOnParityError          = false,
      .receiverAddress            = 0UL,
      .receiverAddressMask        = 0UL,
      .acceptAddrInFifo           = false,
      .enableCts                  = false,
      .ctsPolarity                = CY_SCB_UART_ACTIVE_LOW,
      .rtsRxFifoLevel             = 0UL,
      .rtsPolarity                = CY_SCB_UART_ACTIVE_LOW,
      .rxFifoTriggerLevel  = 0UL,
      .rxFifoIntEnableMask = 0UL,
      .txFifoTriggerLevel  = 0UL,
      .txFifoIntEnableMask = 0UL,
  };
  /* Configure UART to operate */
  (void) Cy_SCB_UART_Init(UART_SCB, &uartConfig, &uartContext);


  /* Assign pins for UART on SCBx */


  /* Connect SCB UART function to pins */
  Cy_GPIO_SetHSIOM(UART_PORT, UART_RX_NUM, UART_RX_PIN);
  Cy_GPIO_SetHSIOM(UART_PORT, UART_TX_NUM, UART_TX_PIN);
  /* Configure pins for UART operation */
  Cy_GPIO_SetDrivemode(UART_PORT, UART_RX_NUM, CY_GPIO_DM_HIGHZ);
  Cy_GPIO_SetDrivemode(UART_PORT, UART_TX_NUM, CY_GPIO_DM_STRONG_IN_OFF);


  /* Assign divider type and number for UART */


  /* Connect assigned divider to be a clock source for UART */
  Cy_SysClk_PeriphAssignDivider(UART_CLOCK, UART_CLK_DIV_TYPE, UART_CLK_DIV_NUMBER);




  // set baud
  Cy_SysClk_PeriphSetDivider   (UART_CLK_DIV_TYPE, UART_CLK_DIV_NUMBER, UART_DIVISION);
  Cy_SysClk_PeriphEnableDivider(UART_CLK_DIV_TYPE, UART_CLK_DIV_NUMBER);


  /* Populate configuration structure (code specific for CM4) */
  cy_stc_sysint_t uartIntrConfig =
  {
      .intrSrc      = UART_INTR_NUM,
      .intrPriority = UART_INTR_PRIORITY,
  };
  /* Hook interrupt service routine and enable interrupt */
  (void) Cy_SysInt_Init(&uartIntrConfig, &UART_Isr);
  NVIC_EnableIRQ(UART_INTR_NUM);


  /* Enable UART to operate */
  Cy_SCB_UART_Enable(UART_SCB);
}

 

Interrupt Handler with FreeRTOS brain

 

The interrupt handler is, by my design choice, FreeRTOS aware. Based on the FreeRTOS tutorial for sending data, but I changed it to work on the receive side.

It's written to release a task as soon as an agreed number of characters arrived:

 

// UART interrupt handler
void UART_Isr() {
  Cy_SCB_UART_Interrupt(UART_SCB, &uartContext);

  BaseType_t xHigherPriorityTaskWoken = pdFALSE;

  configASSERT( xTaskToNotify != NULL );

  /* Notify the task that the receive is complete. */
  vTaskNotifyGiveFromISR( xTaskToNotify, &xHigherPriorityTaskWoken );
  /* There are no receive in progress, so no tasks to notify. */
  xTaskToNotify = NULL;

  /* If xHigherPriorityTaskWoken is now set to pdTRUE then a
  context switch should be performed to ensure the interrupt
  returns directly to the highest priority task.  The macro used
  for this purpose is dependent on the port in use and may be
  called portEND_SWITCHING_ISR(). */
  portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

 

The design has a FreeRTOS task that patiently waits on a notification. The code above in the interrupt is the code that generates that notification.

It fires exactly at the right time: when the agreed number of bytes are available and have been moved to the read buffer.

What happens is:

  • the default interrupt processing for the PSoC 6 is done at line 03.
  • We ask FreeRTOS to notify the waiting task that the agreed bytes have been received, at line 10.
  • Then we inform FreeRTOS that our interrupt handler has finished.

I did not invent this. It's boilerplate RTOS interrupt processing.

 

The PSoC 6 Function to enable UART read with interrupt handling

 

Then the read function that primes the PSoC 6 UART read function, every time the buffer has been filled:

 

// UART activate a receive with interrupt. Wait for ever for UART_BUFFER_SIZE bytes
void UART_receive() {
  /* At this point xTaskToNotify should be NULL as no receive
  is in progress.  A mutex can be used to guard access to the
  peripheral if necessary. */
  configASSERT( xTaskToNotify == NULL );

  /* Store the handle of the calling task. */
  xTaskToNotify = xTaskGetCurrentTaskHandle();

  /* Start receive operation (do not check status) */
  (void) Cy_SCB_UART_Receive(UART_SCB, rxBuffer, sizeof(rxBuffer), &uartContext);
}

 

The first two lines of code are again FreeRTOS common, to set the deactivation of the UART RTOS task until the read received its data over the UART.

The last line actually activates the PSoC 6 read function. Non-blocking, with the trigger primed. It tells the SCB on the PSoC to accept data by itself, buffer it and trigger the trigger when the agreed amount of bytes arrived.

 

FreeRTOS Task that wakes up when a buffer of UART data is received

 

Now the FreeRTOS task:

 

void uart_task(void *pvParameters) {

  /* To avoid compiler warnings */
  (void)pvParameters;
  uint32_t ulNotificationValue;

  while (true) {
    /* Start the receiving from UART. */
    UART_receive();
    /* Wait to be notified that the receive is complete.  Note
        the first parameter is pdTRUE, which has the effect of clearing
        the task's notification value back to 0, making the notification
        value act like a binary (rather than a counting) semaphore.  */
    ulNotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY  );

    if( ulNotificationValue == 1 )
    {
      /* Blocking wait until buffer is full */
      // in this example, it will never handle because the UART interrupt fires exaxtly when the buffer is full
      while (0UL != (CY_SCB_UART_RECEIVE_ACTIVE & Cy_SCB_UART_GetReceiveStatus(UART_SCB, &uartContext))) {}
      /* Handle received data */
    }
  }
}

 

It calls the non-blocking read function we just defined a step earlier. Then it goes to sleep at line 14  because the read function holds it up.

Once the PSoC UART  gets the agreed number of characters, the trigger fires and calls this function in the handler (see a few steps above where the ,interrupt handler is described):

 

vTaskNotifyGiveFromISR( xTaskToNotify, &xHigherPriorityTaskWoken );

 

This will push our FreeRTOS task over line 14. The received data sits in the rxBuffer by that time. This works so well together that it's almost magic. Without burning the microcontroller ticks to poll the UART.

You can then put your logic here:

 

   if( ulNotificationValue == 1 )
    {
      /* Blocking wait until buffer is full */
      // in this example, it will never handle because the UART interrupt fires exaxtly when the buffer is full
      while (0UL != (CY_SCB_UART_RECEIVE_ACTIVE & Cy_SCB_UART_GetReceiveStatus(UART_SCB, &uartContext))) {}
      /* Handle received data */
    }

 

Line 05 is not really needed, because we already know all data arrived. The task notification told it. Because it was generated by the interrupt. And the interrupt fires when the agreed number of bytes are in.

 

In a project I'm working on with balearicdynamics, this will be the place where we push that received data on a FreeRTOS queue.

 

image

 

There are a few globals I defined for this exercise. They are only used in one .c file. If you prefer no global variables, you can use the established C practices to avoid them.

 

cy_stc_scb_uart_context_t uartContext;
static TaskHandle_t xTaskToNotify = NULL;
const UBaseType_t xArrayIndex = 1;
uint8_t rxBuffer[UART_BUFFER_SIZE];

 

 

When you try to use the debugger on the PSoC 6, and it does not break at your breakpoints, here are a few watch outs:

 

optimiser settings

The GCC optimiser is active. Even if your make file has: CONFIG=Debug

The documents seem to tell otherwise, but trust me on this one.

This will cause that the debugger may just ignore the source line you put a breakpoint on, because it's optimised out by the smart compiler.

You can override this by changing the line above to: CONFIG=Custom

and amend this line: CFLAGS=-O0

 

debugging with FreeRTOS and OpenOCD (the debugger that comes with many PSoC 6 kits)

To allow the debugger to deal with FreeRTOS, you have to prevent that your jobs take the highest priority.

You do that by adding these lines in your code before yielding to the scheduler:

 

  /* This enables RTOS aware debugging in OpenOCD. */

  uxTopUsedPriority = configMAX_PRIORITIES - 1;

 

PSoC 6 series
Create a Project with its own Board- and Module configurations
Low Power Management - prepare the board for current measurements
Power consumption without WiFi or Bluetooth
Create a FreeRTOS 10.3 Project
UART receiver with FreeRTOS
FreeRTOS message queue
UART receiver with FreeRTOS - Deep Sleep support
  • Sign in to reply

Top Comments

  • Jan Cumps
    Jan Cumps over 1 year ago in reply to skruglewicz +2
    I found a version that's still online: https://sdkdocs.cypress.com/html/psoc6-with-anycloud/en/latest/api/psoc-base-lib/pdl/group__group__scb__uart.html The documentation is also available locally for…
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to Jan Cumps +2
    skruglewicz , this is the new proper online location: https://infineon.github.io/psoc6pdl/pdl_api_reference_manual/html/group__group__scb__uart.html I'm adjusting the original post.
  • balearicdynamics
    balearicdynamics over 2 years ago +1
    Great piece of cooperation Enrico
  • skruglewicz
    skruglewicz over 1 year ago in reply to Jan Cumps

    Jan Cumps  Thanks for the quick action. I'll be working thru this blog. .... Steve K

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to Jan Cumps

    skruglewicz, this is the new proper online location: https://infineon.github.io/psoc6pdl/pdl_api_reference_manual/html/group__group__scb__uart.html

    I'm adjusting the original post.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to skruglewicz

    I found a version that's still online: https://sdkdocs.cypress.com/html/psoc6-with-anycloud/en/latest/api/psoc-base-lib/pdl/group__group__scb__uart.html

     

    The documentation is also available locally for each lib that ModusToolbox installs:

    image

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • skruglewicz
    skruglewicz over 1 year ago

    hello Jan Cumps

     

    I am trying to follow your blog to implement your example. It seems like the Link you refer to at the beginning of the blog is broken?

    "I follow the PSoC 6 documentation for Serial Communication Block configuration"

    Can you direct me to the documentation you are referring to?

    I'm using the PSOC 6  EVK: CY8Ckit-062S2-43012 . I've been able to follow some of your other blogs in your PSoC6 Series. I'm planning on a design that uses the Arduino Nano to connect to a UART on the PSoC 6.

    Thanks

     

    Steve K

     

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 1 year ago in reply to brandiware

    I am out of my comfort zone here .. I've reviewed examples that use the configurator, but not made one myself.

     

    This example that's available from File -> New -> ModusToolbox Application is interesting: SCB_UART_Transmit_and_Receive_using_DMA

    It uses UART 5, so not a 100% match. And it uses DMA, and that adds complexity.

    What I learned from it, is that the UART definition from the configurator (and any other device that's configured in the utility) is applied at the very first line of code:

     

    int main(void)
    {
        /* Set up the device based on configurator selections */
        init_cycfg_all();

     

    In essence, it's a beautiful example for UART use.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2023 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • Facebook
  • Twitter
  • linkedin
  • YouTube