Cornell University ECE4760
ProtoThreads
1.4 -- Simple examples
Pi Pico rp2040/rp2350

Protothreads 1.4 on RP2040/2350
For a complete explanation of Protothreads capibilities and limitations look here.
You should read sections 1.3-1.6 of the reference manual to see all of the implementation details.
Below the description of the examples there is a table with all available protothreads functions, and below that a few debugging hints.

The first three examples
In this section we are going to consider how to use protothreads by considering with three simple examples. The examples will show:

  1. The first example program just starts one thread that blinks the on-board LED.
    Main initializes the serial interface, identifies one function to the scheduler as a thread, and starts the scheduler in round-robin mode.
    Round-robin mode executes each thread in order.
    The one thread (protothread_toggle25):
    1. starts with a macro to identify an entry point: PT_BEGIN(pt);
    2. defines a static boolean variable for the LED state.
      All local variables in a thread must be static!
    3. Sets up gpio25 as an output controlled by the SIO and sets it to true.
    4. Since a scheduled thread function can never exit, the thread falls into a endless loop which
      toggles the LED state variable, copies it to gpio25, then yields the thread for
      100,000 uSec. A that point the scheduler gets control, notices that there are no other
      threads to run and stalls until the thread yield times out.
    5. the thread ends with a marker for the scheduler: PT_END(pt);

  2. The second example program starts two threads on one core. One to blink the LED at a duration set
    by a global variable and another to get user input for the duration variable.
    Main initializes the serial interface, identifies two functions to the scheduler as threads, and starts the scheduler in round-robin mode.
    The toggle thread is essentially identical to the first example, except that the blink time is a variable. User input
    from the serial UART channel needs careful handling because the serial is so slow compared to the cpu.  
    At 115200 baud, each sent character takes around 90 uSec to send, which is around 11000 cpu cycles we do not want to waste.
    Receiving text from a human typist is much slower, and therefore the receiver has to construct a string, while yielding between
    each typed character to any other thread which is ready to run.
    All of the yielding behavior in the serial thread is hidden from the programmer behind some macros and functions.

    The serial input thread (protothread_serial):
    1. starts with a macro to identify an entry point: PT_BEGIN(pt);
    2. Since a scheduled thread function can never exit, the thread falls into a endless loop which
      prompts for and receives the value of blink time.
    3. uses sprintf to construct a prompt string in a specific buffer, pt_serial_out_buffer.
    4. invokes serial_write to print the prompt, while blocking only this thread and allowing
      other threads to execute between characters.
    5. invokes serial_read to grab the user response, while blocking only this thread and allowing
      other threads to execute between characters.
    6. converts the input sring returned in pt_serial_in_buffer to an integer using sscanf and stores it in the global blink time variable.
    7. Note that there is no explicit yield in the serial thread because the the serial read/write functions handle the yields.
    8. the thread ends with a marker for the scheduler: PT_END(pt);

  3. The third example program starts two threads on core0 and one thread on core1.
    In this example, one of the threads on core0 turns the LED on for a certain time, while the thread on
    core1 turns off the same LED for a certain length of time. The second thread on core0 prompts the
    user for separate on-time and off-time to control the two other threads. There are two global
    variables to conrol the on/off times, and two semaphores to ensure precise alternation
    between the thread that turns on the LED and the thread that turns it off.

    There are now two Main functions, one for core0 and one for core1;
    1. Core1 main adds the protothread_toggle25_off function to the core1 scheduler,
      then starts the core 1 schduler.
    2. Core0 main:
      1. sleeps for a millseccond to let the programmer finish
      2. starts the serial i/o
      3. initializes two core-safe semaphores to control blink excution and
        sets one of them to one and the other to zero.
        Setting a semaphore to one allows immediate execution.
        In this case, to start the blinking.
      4. inits the gpio for the blink threads
      5. resets, then starts core1 and again waits a millisecond
      6. identifies the two core0 threads to the core0 scheduler
      7. starts the core0 scheduler.

    There are three threads. The first one turns the LED on from core0
    1. The protothread_toggle25_on thread runs on core0. It drops directly into
      an endless loop which immediately waits for the blink_on_go_s semaphore to become
      non-zero, which happens when the semaphore is signaled in the thread on core1.
    2. When the semaphore is non-zero two things happen: It is automatically set to zero
      and allows execution of rest of the program.
    3. The gpio pin is set to true
    4. the thread yields for the on-time
    5. the thread signals the other blink thread to execute using
      PT_SEM_SAFE_SIGNAL(pt, &blink_off_go_s) ;

    The second thead turns the LED off from core1.
    1. The protothread_toggle25_off thread runs on core1. It drops directly into
      an endless loop which immediately waits for the blink_off_go_s semaphore to become
      non-zero, which happens when the semaphore is signaled in the thread on core0.
    2. When the semaphore is non-zero two things happen: It is automatically set to zero
      and allows execution of rest of the program.
    3. The gpio pin is set to false
    4. the thread yields for the off-time
    5. the thread signals the other blink thread to execute using
      PT_SEM_SAFE_SIGNAL(pt, &blink_on_go_s) ;

    The third thread allows the user to set the blink on and blink off times.
    1. The protothread_serial thread drops directly into an endless loop
    2. Just as in example 2 above the thread prompts the user
    3. the user responds with two integers for two times in uSec.
    4. sscanf converts the input string into two integers. Since sscanf
      does not yield, you are assured that the two numbers will be stored
      before either of the other threads can see them.

The ZIP file contains all three example codes.

  1. Unzip the file
  2. Import the project using the Pico exentension in VScode
  3. Change the board type to either pico or pico2 (with no risc5)
  4. The cmakelists.txt file has four of the five example codes commented out.
    Uncomment the one you want to compile and comment the others.
  5. compile and run
  6. For examples 2 and 3, you will need to open the serial monitor.

Some more advanced examples
In this section we are going to look at some features of Protothreads which are very handy, but not as heavily used as in the previous examples.


Debugging ProtoThreads

If your program just sits there and does nothing:
-- Did you write each thread to include at least one YIELD (or YIELD_UNTIL, or YIELD_TIME_msec) in the while(1) loop? Remember that the UART input spawned
thread yields betwen eacy typed character. Humans are sooo slow.
-- Did you schedule the threads?
-- Does the board have correct Vdd?

If your program reboots about a few times/sec:
-- Did you exit from any scheduled thread? This blows out the stack.
-- Did you turn on any interrupt source and fail to write an ISR for that source?
-- Did you divide by zero? A divide by zero is an untrapped exception.
-- Did you write to a non-existant memory location, perhaps by using a negative (or very large) array index.
A write to a non-existant memory location is an untrapped exception.

If your thread state variables seem to change on their own:
--- Did you define any automatic local variable?
Local variables in a thread should be static.
-- Are your global arrays big enough the clobber the stack in high memory?
You should be able to use over 200 kbytes.


Copyright Cornell University May 22, 2025