Cornell University ECE4760
ProtoThreads
and Timers
PIC32MX250F128B

Protothreads on PIC32
Protothreads is a very light-weight, stackless, threading library written entirely as C macros by Adam Dunkels. As such, it is trivial to move to PIC32. Adam Dunkels' documentation is very good and easy to understand. There is support for a thread to wait for an event, spawn a thread, and use semaphores. The Protothreads system is a cooperative multithread system. As such, there is no thread preemption. All thread switching is at explicit wait or yield statement. There is no scheduler. You can write your own, or just use a simple round-robin scheme, calling each thread in succession in main. Because there is no preemption, handling shared variables is easier because you always know exactly when a thread switch will occur. Because there is no separate stack for each thread, the memory footprint is quite small, but using automatic (stack) local variables must be avoided. You can use static local variables.

You must read sections 1.3-1.6 of the reference manual (local copy) to see all of the implementation details.
I wrote several examples shown below and added:

Version 1_2_2:
Conversion to use the big development board.
U2TX moved to RB10. U2RX moved to RA1. Use the cable specified on the serial page.
Use config_1_2_2.h and pt_cornell_1_2_2.h on the big development board.
Example code is on the Development board page. Search for PuTTY on that page.

Version 1.2.1:
Fixes a bug caused by starting a second DMA-to-UART DMA burst, before the UART transmit FIFO is empty. pt_cornell_1_2_1.h.
// Wait for the DMA tranfer to complete -- existed in 1.2
PT_YIELD_UNTIL(pt, DmaChnGetEvFlags(DMA_CHANNEL1) & DMA_EV_BLOCK_DONE);
// Wait until the UART transmit buffer is empty -- added in 1.2.1 based on section 21.5.2 of Reference Manual
PT_YIELD_UNTIL(pt, U2STA&0x100);

Version 1.2:
To run protothreads 1.2 you need to download config.h, pt_cornell_1_2.h, plus the TFT routines, or the project ZIP (see below) file.
The main change from Version 1.1 is a fix for the limitation on having any thread-yield statement inside a switch statement.
This version allows yield, spawn, and wait statements anywhere. This version depends on documented, but seldom used, features of GCC.
You must still select the hardware features you need in config.h.
--Turning on the use_vref_debug feature disables pin 25 for everything except Vref output.
--Turning on the use_uart_serial feature disables pins 21 and 22 for everything except the USART
--Make sure that all of the special feature pins are disabled so that you can use them as i/o. Select:
-- #pragma config POSCMOD = OFF, FWDTEN = OFF, FSOSCEN = OFF, JTAGEN = OFF, DEBUG = OFF

Version 1.1:
To run protothreads 1.1 you need to download config.h, pt_cornell_1_1.h, plus the TFT routines, or one of the project ZIP (see below) files.
The ProtoThreads include file structure has been simplified.This version of Protothreads uses a switch-statement type construct to handle thread switching, so it is not possible to embed a thread-wait statement in a switch stanza.
The config.h file now sets:

There are examples:

The following tables give the syntax for the thread macros and functions.

Protothreads Functions (from Adam Dunkels) Description
PT_INIT(pt)
Initialize a protothread.
PT_THREAD(name_args)
Declaration of a protothread. name_args The name and arguments of the C function implementing the protothread.
PT_WAIT_THREAD(pt, thread)
Block and wait until a child protothread completes. pt A pointer to the protothread control structure. thread The child protothread with arguments
PT_WAIT_UNTIL(pt, condition)
PT_WAIT_WHILE(pt, condition)
Block and wait until/while condition is true.
PT_BEGIN(pt)
Declare the start of a protothread inside the C function implementing the protothread. pt A pointer to the protothread control structure.
PT_END(pt)
Declare the end of a protothread. pt A pointer to the protothread control structure.
PT_EXIT(pt)
This macro causes the protothread to exit. If the protothread was spawned by another protothread, the parent protothread will become unblocked and can continue to run. pt A pointer to the protothread control structure.
PT_RESTART(pt)
This macro will block and cause the running protothread to restart its execution at the place of the PT_BEGIN()call. pt A pointer to the protothread control structure.
PT_SCHEDULE(f)
This function shedules a protothread. The return value of the function is non-zero if the protothread is running or zero if the protothread has exited. f The call to the C function implementing the protothread to be scheduled
PT_SPAWN(pt, child, thread)
Spawn a child protothread and wait until it exits. Parameters:
pt A pointer to the protothread control structure.
child A pointer to the child protothread’s control structure.
thread The child protothread with arguments
PT_YIELD(pt)
yield the protothread, thereby allowing other processing to take place in the system.
PT_YIELD_UNTIL(pt, cond)
Yield from the protothread until a condition occurs.
PT_SEM_INIT(s, c)
This macro initializes a semaphore with a value for the counter. Internally, the semaphores use an "unsigned int" to represent the counter, and therefore the "count" argument should be within range of an unsigned int. s A pointer to the pt_sem struct representing the semaphore
PT_SEM_SIGNAL(pt, s)
This macro carries out the "signal" operation on the semaphore. The signal operation increments the counter inside the semaphore, which eventually will cause waiting protothreads to continue executing. pt A pointer to the protothread (struct pt) in which the operation is executed. s A pointer to the pt_sem struct representing the semaphore
PT_SEM_WAIT(pt, s)
This macro carries out the "wait" operation on the semaphore. The wait operation causes the protothread to block while the counter is zero. When the counter reaches a value larger than zero, the protothread will continue.

The following table has the protothread macro extensions and functions I wrote for the PIC32 and which are included in the Cornell header file.

Added Protothreads functions

Description

 

PT_YIELD_TIME_msec(delay_time) Causes the current thread to yield (stop executing) for the delay_time in milliseconds. The time is derived from a 1 mSec ISR running from timer5.
PT_GET_TIME() Returns the current millisecond count since boot time. Overflows in about 5 weeks. The time is derived from a 1 mSec ISR running from timer5.
PT_RATE_INIT() Sets up variables for the optional rate scheduler
PT_RATE_LOOP() House keeping for the optional rate scheduler
PT_RATE_SCHEDULE(f,rate) For thread f, set the rate=0 to execute always, rate=1 to execute every other traversal for PT_RATE_LOOP, rate=2 to every fourth traversal, rate=3 to every 8th, and rate=4 to every 16th.
PT_DEBUG_VALUE(level, duration) Causes a voltage level from 0 to 15 (1 implies ~150 mV) to appear at pin 25 (CVrefOut) for duration microseconds (approximately). Zero duration means hold the voltage until changed by another call. To use this, config.h must contain #define use_vref_debug
int PT_GetSerialBuffer(struct pt *pt) A thread which is spawned to get nonblocking string input from UART2. String is returned in
char PT_term_buffer[max_chars]. If more than one thread can spawn this thread, then there must be semaphore protection. Control returns to the scheduler after every character is received. The thread dies when it receives an <enter>. To use this, config.h must contain #define use_uart_serial
int PutSerialBuffer(struct pt *pt) A thread which is spawned to send a string input from UART2. String to be sent is in
char PT_send_buffer[max_chars]. If more than one thread can spawn this thread, then there must be semaphore protection. Control returns to the scheduler after every character is loaded to be sent. The thread dies after it sends the entire string. To use this, config.h must contain #define use_uart_serial
int PT_DMA_PutSerialBuffer(struct pt *pt) A thread which is spawned to send a string input from UART2. String to be sent is in
char PT_send_buffer[max_chars]. If more than one thread can spawn this thread, then there must be semaphore protection. Control returns to the scheduler immediately. The thread dies after it sends the entire string. To use this, config.h must contain #define use_uart_serial
void PT_setup (void) Configures system frequency, UART2, a DMA channel 1 for the UART2 send, timer5, and the debug pin Vref controller, depending on the features chosen in config.h.

Bugs to be fixed:


Timers, Output Compare, PWM, and Input Capture
All of the following examples use Protothreads. PIC architecture separates timers, from compare units and from input capture. This means that one timer can drive several output compare units for waveform generation, or act as a time reference for several input compare units. In all the examples, the cpu is running at 64 MHz and the peripherial bus at 32 MHz. It might be safer to run everything at 40 MHz.
-- This example sets up timer2 to drive two pulse trains from OC2 and OC3. Either of these pulse trains can be hooked to an input capture unit, which uses timer3 as a time reference. Timer three is set up to overflow so that periods are correct when computed from sequential edge capture times. The print thread prints out the generated interval, and the min, max and current value of the captured interval. The command thread listens for user input to set the timer2 period, and a one second clock thread gives system time (using timer5, as explained below in the protothreads section). The example code.
-- Example 2 sets up OC3 as a PWM unit with settable timer2 period (and thus PWM resolution) and settable PWM on-time. The on-time is then auto incremented in the timer2 ISR to sweep the on-time from zero to the timer2 period. Setting the timer2 period and OC2 pulse period in the user interface thread is cleaner.
-- Example 3 sets up OC3 as a PWM unit with timer2 period (and thus PWM resolution) equal to 64 cycles (500 kHz). PWM on-time is set by a sine wave Direct Digital Synthesis (DDS) unit. The frequency synthesized is set by the UART user interface. The PWM output (Pin 18) must be passed through an analog lowpass filter. Choose the time constant of the filter consistent with the frequencies you wish to generate. Spectral purity is about 32 db at low frequencies. You could get better spectral purity by increasing the PWM resolution, but that, of course, lowers the sample rate. Eight-bit samples have a PWM sample rate of 125 kHz.


=============================

Version 1.0
To run protothreads you need to download pt_cornell.h and you need to download software from Dunkels' site or use a local copy. The Example1 test code also requires a UART connection to a terminal, as explained in a project further down the page. The test code toggles three i/o pins and supports a small user interface through the UART.It also emits three different amplitude debugging pulses on pin 25. By default this version of protothreads starts timer5 and uses a timer ISR to count milliseconds.

=============================
To run these older examples you need to download software from Dunkels' site or use a local copy. Most examples also require a UART connection to a terminal, as explained in a project further down the page.
Older examples:
-- The first example has two threads executing at a rate based on a hardware timer ISR, which generates a millisecond time counter. Each thread yields for a waiting time and when executing prints the thread number and time. Thread 1 executes once per second. Thread 2 executes every 4 seconds. Main just sets up the timer ISR and UART, then inintialzes the threads and schedules them.
-- The second example has three threads. Threads 1 and 2 wait on semaphores, each of which is signaled by the other thread. The two threades therefore alternate. Thread 3 just executes every few seconds. I defined an new macro to make it easier for a thread to wait for a specific time. PT_YIELD_TIME(wait_time) takes the wait time parameter and uses a local variable and the millisceond timer variable to yield the processor to another thread for wait_time milliseconds. The second example also has a small routine to compute approximate microseconds since reset and return it as a 64-bit long long int.

#define PT_YIELD_TIME(delay_time) \
    do { static int time_thread; \
    PT_YIELD_UNTIL(pt, milliSec >= time_thread); \
    time_thread = milliSec + delay_time ;} while(0);

-- The third example has three threads. Threads 1 and 2 wait on semaphores, each of which is signaled by the other thread. The two threads therefore alternate. Thread 3 takes input from a serial terminal. The actual input routine is a thread which is spawned by thread 3. Thread 3 then waits for the input thread to terminate which it does when the human presses <enter>. The input thread yields the processor while it is waiting for the slow human to type each character, so other threads do not stall. The key statment is below which causes protothreads to wait/yield on a hardware flag. The flag is defined as part of plib.h.
PT_YIELD_UNTIL(pt, UARTReceivedDataIsAvailable(UART2));
Note that the spawn command
PT_SPAWN(pt, &pt_input, GetSerialBuffer(&pt_input) );
initializes the input thread and schedules it. The three parameters are the current thread structure, a pointer to the spawned thread, and the actual thread function. If more than one thread is using serial input, then the spawn command should be surrounded by semaphore wait/signal commands because GetSerialBuffer is not reentrant.
-- The fourth example investigates non-blocking UART transmit. In a printf, there is a waitloop for each character. We can replace that with thread yield on a per character basis. Doing this speeds up processing a factor of 2 or so. But how fast is the swtich between two threads? Is it worth a thread yield on every character? Commenting out all UART code and just waiting/signaling on a semaphore between thread 1 and thread 2 gives a switch time between threads (twice) of 2.1 microcseconds or about 126 cpu cycles. This value includes the signaling, waiting, and thread switch code two times (thread 1 to thread 2 and back). For a 1 mSec charcter transmit time, the thread switch is worth the overhead.
-- The fifth example implements a terminal command interface in thread 3 using non-blocking UART send/receive. Thread 1 and 2 toggle and are dependent upon signalling each other unless turned off by a flag from the interactive input. . Thread 4 just toggles at a fixed rate, unless it is turned off by the interactive input, working through the scheduler. The code assumes that port pins B0, A0, and A1 are connected to LEDs (with 300 ohm resistor to ground). Hitting the <enter> key to finish a command results in a 9 microSec pause in the toggling of the other threads.
There are 8 commands:

command effect
t1 time sets blink rate of thread 1/2 to time
t2 time sets blink rate of thread 4 to time
g1 starts thread 1/2 blink
s1 stops thread 1/2 blink
g2 starts thread 4 blink
s2 stops thread 4 blink
k kills the interactive input until RESET
p prints the current blink times

-- The sixth example runs the same interface as example five, but uses a DMA channel to drive the UART output with no software overhead. The DMA pattern matching feature detects the end of a string to stop the UART automatically. Using the DMA transfer allows a per-string thread yield, rather than a per-character thread yield. The code assumes that port pins B0, A0, and A1 are connected to LEDs (with 300 ohm resistor to ground). The thread switch after hitting <enter> now takes 5 microSec. With both t1 and t2 set to 1 milliSec, the dispersion in actual times for both is less than 10 microSec (<1%).
-- The seventh example adds a microsecond resolution yield option. The option is marginally useful down to about 10 microseconds, where the timing uncertainty reaches about 10%. At 100 microseconds the accuracy is good. This means that you could attempt audio synthesis in a thread at 10 KHz sample rate. Thread 4 is timed by the microsecond timer. With three threads running below100 microSec repeat rate, the system starts to miss events. The previous PT_YIELD_TIME macro has been replaced by two. One for millisecond timing and one for microsecond timing. The millisecond timer overflows about once/month. The microsecond timer overflows every 64 milliseconds. The maximum time delay using the microsecond timer is 64000 microseconds.

// macro to time a thread execution interval
#define PT_YIELD_TIME_msec(delay_time) \
    do { static int time_thread; \
    time_thread = milliSec + delay_time ; \
    PT_YIELD_UNTIL(pt, milliSec >= time_thread); \
    } while(0);
// macro to time a thread execution interveal
// parameter is in MICROSEC < 64000
//ReadTimer2()
#define PT_YIELD_TIME_usec(delay_time) \
    do { static unsigned int time_thread, T3, c ; \
      time_thread = T3 + delay_time ; c = 0;\
      if(time_thread >= 0xffff) { c = 0xffff-T3; }\
      PT_YIELD_UNTIL(pt, ((ReadTimer3()+c)& 0xffff) >= ((time_thread+c) & 0xffff)); \
      T3 = ReadTimer3() ;\
    } while(0); 

-- The eighth example introduces a minimal scheduler which allows each thread to execute at a rate determined as a fraction of full speed. The default protothreads thread swap is so fast that it a challange to introduce scheduling which does not slow down thread execution rates. The approach taken is to allow some threads to execute every time through the main while-loop, but allow others to only execute at 1/2, 1/4, 1/8, or 1/16 of the times through the main loop. The approach is consistent with a nonpremptive thread system and gives better execution consistency if one thread has to execute at a much higher rate than the others. Rate 0 executes every time throught the loop, rate 1 every other time, 2 every four times, rate 3 every 8 times, and rate 4 every 16 times through the main while-loop. Any other value freezes the thread execution. With thread 4 executing at a nominal 10 microSec period, the actual time varys from 11 to 13 microSec, but the actual time can vary widely depending on the exact interval picked due to coincidence with other processes. This version also fixes the microsecond timer by using timer45 as a 32-bit counter.
-- Finally we get to something like a final version of the code.


References:

  1. Beginner's Guide to Programming the PIC32 Paperback by Thomas Kibalo
    and more info
  2. Programming 32-bit Microcontrollers in C: Exploring the PIC32 by Lucio Di Jasio
    and more info
  3. PIC32 Architecture and Programming by Johm Loomis Numb3rs
  4. Intro to PIC32 architectture
  5. PIC32 tutorials
  6. UMass M5 PIC32 tutorials and specifically for the PIC32MX220
  7. Northwestern University mechatronics design wiki:
    1. code examples,
    2. benchmarking,
    3. Embedded programming on PIC32
  8. Tahmid's Blog
  9. chipKit
  10. Arduino library for PIC32
    1. Microstick configuration
    2. project zip
  11. DSP experiments and more and
  12. RTOS
    1. http://www.freertos.org/ and Microchip PIC32 FreeRTOS Reference Designs and MPLABX and ECE443 ref
    2. TNKernel
    3. ERIKA Enterprise
    4. Q-Kernel
    5. Protothreads by Adam Dunkels
    6. Protothreads -- similar to Dunkels, but different implementation only for GCC
  13. MIPS32 docs
    1. Architecture
    2. instruction set
    3. privliged resources
  14. Microchip Docs
    1. PIC32MX250F128B PDIP pinout by pin
    2. PIC32MX250F128B ::: Signal Names=>Pins ::: 1, 2, 3, 4, 5, 6, 7 PDIP highlighted in green (for PPS see next tables)
    3. PIC32MX250F128B Peripheral Pin Select (PPS) input table
      example: UART receive pin ::: specify PPS group, signal, logical pin name
      PPSInput(2, U2RX, RPB11); //Assign U2RX to pin RPB11 -- Physical pin 22 on 28 PDIP
    4. PIC32MX250F128B Peripheral Pin Select (PPS) output table
      example: UART transmit pin ::: specify PPS group, logical pin name, signal
      PPSOutput(4, RPB10, U2TX); //Assign U2TX to pin RPB10 -- Physical pin 21 on 28 PDIP
    5. PIC32MX1XX/2XX Interrupt Table 1, 2 and int_1xx_2xx.h -- interrupt IRQ names and vector names
    6. plib.h names of peripherial header files
      -----------------------------------------------------------------
    7. Getting started with PIC32
    8. MPLABX IDE users guide
    9. PICkit3 Users Guide and poster
    10. 32_bit peripherials library
    11. 32 bit language tools and libraries including C libraries, DSP, and debugging tools
    12. XC32 Compiler Users Guide
    13. microstickII pinout
    14. PIC32 reference manual
      and more from Northwestern University mechatronics design wiki, PIC32 page
    15. MIPS-M4K Core
    16. 2xx_datasheet
    17. Microchip doc site on this page choose Documentation from the left column.
      The Reference Manual is particuarly useful
    18. USB Embedded Host Stack
    19. chipKIT (PIC32 arduino library)
    20. code examples (choose PIC32 in product family dropdown)
    21. code libraries (choose PIC32 in product family dropdown)
    22. application notes (choose PIC32 in Select a Product Family panel)
    23. Harmony for PIC32 -- docs --
    24. Microchip TCP/IP Stack Application Note
  15. External Refs back to this work
    1. http://dangerousprototypes.com/2014/07/15/pic32-oscilloscope/
    2. http://hackedgadgets.com/2014/07/14/pic32-oscilloscope/

Copyright Cornell University October 31, 2017