element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • 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 Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • 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
  • Settings
Test & Tools
  • Technologies
  • More
Test & Tools
Blog mini project: PICO-PI programmable Lab Switch - 1b: retrieve UART characters in a FreeRTOS task, with Interrupt support
  • Blog
  • Forum
  • Documents
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Test & Tools to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 17 Oct 2022 2:51 PM Date Created
  • Views 2663 views
  • Likes 8 likes
  • Comments 7 comments
  • pico_freertos_smp
  • pico_freertos_scpi_switch
Related
Recommended

mini project: PICO-PI programmable Lab Switch - 1b: retrieve UART characters in a FreeRTOS task, with Interrupt support

Jan Cumps
Jan Cumps
17 Oct 2022

In this series, I design a programmable lab switch based on RP2040, that can turn a set of signals or supplies on or off. Over USB. SCPI compatible.
In this post: convert the pico-pi uart_advanced example (UART with interrupts) to a FreeRTOS design

image

Check the previous post for the analysis of the bare metal uart_advanced example. It enables receiving side (incoming data) interrupt. When data arrives, the interrupt fires and the service handler reads the input character(s).

I'm going to translate this to 3 FreeRTOS chunks:

  1. the UART initialisation will go into the main function, before the RTOS tasks are created and scheduled.
  2. the interrupt handler will just wake up the sleeping UART task by sending it an RTOS message, instead of performing logic. Then it re-arms itself.
    This is slightly different than the example. Reading data is done in the service handler there.
  3. the UART task will ask RTOS to sleep until it gets a message to wake up (it will get that from the interrupt handler above). After waking up, it reads the incoming data, and does "meaningful business logic" with it.
    Then it asks RTOS to sleep again.
  4. goto 2.

The meaningful business logic in the final design will be: send the incoming data to the SCPI parser, because it's a SCPI command.
In today's blog, where we just want to test it out, we'll echo it back to the UART where we got it from (the USB port). And if the character is a 'b', we also toggle the LED.
Simple, but good enough to prove that this inbound UART part of the design is functional.

UART initialisation

All the meaningful things from the uart_advanced examples happen here. I've put this in a dedicated procedure, and call it early in main().

void initUART() {
    // Set data format
    uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);        
    // Turn off FIFO's - we want to do this character by character
    uart_set_fifo_enabled(UART_ID, false);
    // Set up a RX interrupt
    // We need to set up the handler first
    // Select correct interrupt for the UART we are using
    int UART_IRQ = UART_ID == uart0 ? UART0_IRQ : UART1_IRQ;
    // And set up and enable the interrupt handlers
    irq_set_exclusive_handler(UART_IRQ, UART_Isr);
    irq_set_enabled(UART_IRQ, true);
}

I don't enable the interrupt handlers yet. I'm doing it later, in the UART task.

UART task

In the init part of the task,  we do little. The hardware is already initialised. We just take care that some variables are set.

// some of this code is in a header file. Check the attached zip to see what's where.

/* Task parameters for UART Task. */
#define UART_TASK_PRIORITY       (2)
#define UART_TASK_STACK_SIZE     (1024 * 3)

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

// ... 

#define UART_ID uart0
#define BAUD_RATE 115200
#define DATA_BITS 8
#define STOP_BITS 1
#define PARITY    UART_PARITY_NONE

/* Stores the handle of the task that will be notified when the
 receive is complete. */
volatile TaskHandle_t xTaskToNotify_UART = NULL;

uint8_t rxChar;

void uart_task(void *pvParameters) {

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

    // TODO semaphore

    while (true) {

Then, in the loop part, we wait for incoming messages (which means the trigger informed us there's input):

    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) {
            /* Handle received data */
            while (uart_is_readable(UART_ID)) {
                rxChar = uart_getc(UART_ID);
                // TODO remove test code
                if (uart_is_writable(UART_ID)) {
                    uart_putc(UART_ID, rxChar); // echo incoming char
                }
                if (rxChar == 'b') {
                    gpio_xor_mask(1u << PICO_DEFAULT_LED_PIN); // toggle led
                }
            }
        }
    }
}

We read characters one by one. Then we perform the dummy business logic: echo it back to the serial port, and toggle the on-board LED status if it happens to be the character 'b'.
In a future blog, I'll remove the dummy part, and send the characters to the SCPI parser, so that it can pick them up and examine what command(s) we received.

The read preparation function

This helper function (re)initialises the UART for the next activity. It does so little that I just could have put the 3 commands in the task above.
In the case of this RP2040, where the activities to re-arm are so small, it would make it easier to understand the code to just do this inside the the task. Maybe I do this in a next post ...

// 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_UART == NULL);

    /* Store the handle of the calling task. */
    xTaskToNotify_UART = xTaskGetCurrentTaskHandle();
    // Now enable the UART to send interrupts - RX only
    uart_set_irq_enables(UART_ID, true, false);
}

The UART interrupt handler

It's on purpose a very short function. Most things are common in all FreeRTOS-savvy service handlers. It disables the interrupt and notifies the UART task, so that it wakes up and does its thing.

void UART_Isr() {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    // Now disable the UART to send interrupts
    uart_set_irq_enables(UART_ID, false, false);

    if (xTaskToNotify_UART != NULL) {

        /* Notify the task that the receive is complete. */
        vTaskNotifyGiveFromISR(xTaskToNotify_UART, &xHigherPriorityTaskWoken);
        /* There are no receive in progress, so no tasks to notify. */
        xTaskToNotify_UART = 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 interrupt will be re-enabled inside that task.

main()

The UART initialisation is called, the task registered and the RTOS started. 

int main() {
    /* Configure the hardware */
    prvSetupHardware(); // our initUART is called inside this function

    /* Create the UART task. */
    xTaskCreate(uart_task, "UART task", UART_TASK_STACK_SIZE,
        NULL, UART_TASK_PRIORITY, NULL);

    /* Start the tasks and timer running. */
    vTaskStartScheduler();

    for (;;) {} // RTOS code never gets to here
    return 0;
}

The project in its current state is attached. You can send text to the Pico-PI USB COM port. It replies back with the same character. If you type a 'b', the LED will toggle.
It's worth checking it out, and see some decisions that I do not describe in the posts (build file setup, code organisation)

freertos_scpi_switch20221017.zip

Show all blog posts

  • Sign in to reply
  • Jan Cumps
    Jan Cumps over 2 years ago in reply to DAB

    > Have you made any timing tests on these functions?

    No. I could time between the last UART interrupt fired (command received), and the moment the output changes state.

    • command is parsed, parameters extracted
    • GPIO HAL function is called to toggle a LED.

    But the vast majority of the time is spent sending the ascii SCPI command over the UART at 115200 baud. 
    There's no hard demand on how fast the UART reader should react on the interrupt. I just chose to use interrupts so that I don't have to spend clock ticks polling the UART continuously. RTOS kindly directs those ticks to other tasks.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • DAB
    DAB over 2 years ago

    Interesting post Jan.

    Have you made any timing tests on these functions?

    I used to do timing analysis on computers and OS, so I am always interested in the time delays incurred when an interrupt occurs.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 2 years ago in reply to Jan Cumps

    A side effect of using the SCPI lib that I use in most of my projects, is that you get a standard compliant device out of box. Mandatory commands, including (extendable) error handling "just work".

    Example below: I made a typo: SYS:  instead of SYST:

    image

    The default error handler caught the syntax mistake. And when asking the error subsystem how many errors I had and what it was, I got the correct answer.

    You can extend the system. If your test device has 6 outputs, and you get a command to set output 7, you can log that in the error subsystem. The client software or user can then retrieve that.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 2 years ago in reply to Jan Cumps

    edit (can't edit): seems to be DPDT relais ...

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 2 years ago in reply to shabaz

    add 12 V DC,  and a (mosfet) transistor, a resistor plus a snubber diode per relais, and you can have a six channel SPDT lab switch Trophy

    • 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 © 2025 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

  • X
  • Facebook
  • linkedin
  • YouTube