Protothreads - examples (2024)

Examples:

  • A reliable communication protocol
  • Delays - text scrolling on an LCD panel
  • A code lock
  • The bounded buffer with protothreadsemaphores
  • A radio driver written both with protothreadsand events

A reliable communication protocol

In computer communication, there are many cases in which a protocolneeds to cope with packet loss. In order to provide a reliablechannel, lost packets must be retransmitted. This example shows asimple communication protocol that uses (the absence of)acknowledgment packets to detect a lost packet. If a lost packet isdetected, the packet is retransmitted.

The example shows two protothreads, one for the sender and one for thereceiver.

#include "pt.h" PT_THREAD(sender(struct pt *pt)){ PT_BEGIN(pt); do { send_packet(); /* Wait until an ackowledgement has been received, or until the timer expires. If the timer expires, we should send the packet again. */ timer_set(&timer, TIMEOUT); PT_WAIT_UNTIL(pt, acknowledgment_received() || timer_expired(&timer)); } while(timer_expired(&timer)); PT_END(pt);} PT_THREAD(receiver(struct pt *pt)){ PT_BEGIN(pt); /* Wait until a packet has been received, and send an acknowledgment. */ PT_WAIT_UNTIL(pt, packet_received()); send_acknowledgement(); PT_END(pt);}

Delays - text scrolling on an LCD panel

Protothreads can be used for introducing delays inside a function,without using a full threading model. The following example shows afunction writing text to a one-line LCD panel. If the text is longerthan the size of the panel, the text should be scrolling in from theright.

#include "pt.h"#include "timer.h" struct state { char *text; char *scrollptr; struct pt pt; struct timer timer;}; PT_THREAD(display_text(struct state *s)){ PT_BEGIN(&s->pt); /* If the text is shorter than the display size, show it right away. */ if(strlen(s->text) <= LCD_SIZE) { lcd_display_text(s->text); } else { /* If the text is longer than the display, we should scroll in the text from the right with a delay of one second per scroll step. We do this in a for() loop, where the loop variable is the pointer to the first character to be displayed. */ for(s->scrollptr = s->text; strlen(s->scrollptr) > LCD_SIZE;++s->scrollptr) { lcd_display_text(s->scrollptr); /* Wait for one second. */ timer_set(&s->timer, ONE_SECOND); PT_WAIT_UNTIL(&s->pt, timer_expired(&s->timer)); } } PT_END(&s->pt);}

A code lock

This example is a bit more complicated and shows how to implement asimple code lock - the kind of device that is placed next to doors andthat you have to push a four digit number into in order to unlock thedoor.

The code lock waits for key presses from a numeric keyboard andif the correct code is entered, the lock is unlocked. There is amaximum time of one second between each key press, and after thecorrect code has been entered, no more keys must be pressed for 0.5seconds before the lock is opened.

The code uses external functions for timers and for checking thekeyboard. These functions are included in the full source code forthis example, which is included in the downloadabletarball.

/* * This is the code that has to be entered. */static const char code[4] = {'1', '4', '2', '3'}; /* * Declaration of the protothread function implementing the code lock * logic. The protothread function is declared using the PT_THREAD() * macro. The function is declared with the "static" keyword since it * is local to this file. The name of the function is codelock_thread * and it takes one argument, pt, of the type struct pt. * */staticPT_THREAD(codelock_thread(struct pt *pt)){ /* This is a local variable that holds the number of keys that have * been pressed. Note that it is declared with the "static" keyword * to make sure that the variable is *not* allocated on the stack. */ static int keys; /* * Declare the beginning of the protothread. */ PT_BEGIN(pt); /* * We'll let the protothread loop until the protothread is * expliticly exited with PT_EXIT(). */ while(1) { /* * We'll be reading key presses until we get the right amount of * correct keys. */ for(keys = 0; keys < sizeof(code); ++keys) { /* * If we haven't gotten any keypresses, we'll simply wait for one. */ if(keys == 0) { /* * The PT_WAIT_UNTIL() function will block until the condition * key_pressed() is true. */PT_WAIT_UNTIL(pt, key_pressed()); } else {/* * If the "key" variable was larger than zero, we have already * gotten at least one correct key press. If so, we'll not * only wait for the next key, but we'll also set a timer that * expires in one second. This gives the person pressing the * keys one second to press the next key in the code. */timer_set(&codelock_timer, 1000); /* * The following statement shows how complex blocking * conditions can be easily expressed with protothreads and * the PT_WAIT_UNTIL() function. */PT_WAIT_UNTIL(pt, key_pressed() || timer_expired(&codelock_timer)); /* * If the timer expired, we should break out of the for() loop * and start reading keys from the beginning of the while(1) * loop instead. */if(timer_expired(&codelock_timer)) { printf("Code lock timer expired.\n"); /* * Break out from the for() loop and start from the * beginning of the while(1) loop. */ break;} } /* * Check if the pressed key was correct. */ if(key != code[keys]) {printf("Incorrect key '%c' found\n", key);/* * Break out of the for() loop since the key was incorrect. */break; } else {printf("Correct key '%c' found\n", key); } } /* * Check if we have gotten all keys. */ if(keys == sizeof(code)) { printf("Correct code entered, waiting for 500 ms before unlocking.\n"); /* * Ok, we got the correct code. But to make sure that the code * was not just a fluke of luck by an intruder, but the correct * code entered by a person that knows the correct code, we'll * wait for half a second before opening the lock. If another * key is pressed during this time, we'll assume that it was a * fluke of luck that the correct code was entered the first * time. */ timer_set(&codelock_timer, 500); PT_WAIT_UNTIL(pt, key_pressed() || timer_expired(&codelock_timer)); /* * If we continued from the PT_WAIT_UNTIL() statement without * the timer expired, we don't open the lock. */ if(!timer_expired(&codelock_timer)) {printf("Key pressed during final wait, code lock locked again.\n"); } else { /* * If the timer expired, we'll open the lock and exit from the * protothread. */printf("Code lock unlocked.\n");PT_EXIT(pt); } } } /* * Finally, we'll mark the end of the protothread. */ PT_END(pt);}

The bounded buffer with protothread semaphores

The following example shows how to implement the bounded bufferproblem using the protothreads semaphore library. The example usesthree protothreads: one producer() protothread that producesitems, one consumer() protothread that consumes items, and onedriver_thread() that schedules the producer and consumerprotothreads.

Note that there is no need for a mutex to guard theadd_to_buffer() and get_from_buffer() functions becauseof the implicit locking semantics of protothreads - a protothread willnever be preempted and will never block except in an explicit PT_WAITstatement.

#include "pt-sem.h" #define NUM_ITEMS 32#define BUFSIZE 8 static struct pt_sem full, empty; static PT_THREAD(producer(struct pt *pt)){ static int produced; PT_BEGIN(pt); for(produced = 0; produced < NUM_ITEMS; ++produced) { PT_SEM_WAIT(pt, &full); add_to_buffer(produce_item()); PT_SEM_SIGNAL(pt, &empty); } PT_END(pt);} static PT_THREAD(consumer(struct pt *pt)){ static int consumed; PT_BEGIN(pt); for(consumed = 0; consumed < NUM_ITEMS; ++consumed) { PT_SEM_WAIT(pt, &empty); consume_item(get_from_buffer()); PT_SEM_SIGNAL(pt, &full); } PT_END(pt);} static PT_THREAD(driver_thread(struct pt *pt)){ static struct pt pt_producer, pt_consumer; PT_BEGIN(pt); PT_SEM_INIT(&empty, 0); PT_SEM_INIT(&full, BUFSIZE); PT_INIT(&pt_producer); PT_INIT(&pt_consumer); PT_WAIT_THREAD(pt, producer(&pt_producer) & consumer(&pt_consumer)); PT_END(pt);}

A radio driver written both with protothreadsand events

This example shows an interrupt handler in a device driver for a TR1001 radiochip. The driver receives incoming data in bytes and constructs aframe that is covered by a CRC checksum. The driver is implementedboth with protothreads and with an explicit state machine. The statemachine has 11 states and is implemented using the C switch()statement. In contrast, the protothreads-based implementation does nothave any explicit states.

The flow of control in the state machine-based implementation is quitehard to follow from inspection of the code, whereas the flow ofcontrol is evident in the protothreads based implementation.

Protothreads-based implementation

PT_THREAD(tr1001_rxhandler(unsigned char incoming_byte)){ PT_YIELDING(); static unsigned char rxtmp, tmppos; PT_BEGIN(&rxhandler_pt); while(1) { /* Wait until we receive the first syncronization byte. */ PT_WAIT_UNTIL(&rxhandler_pt, incoming_byte == SYNCH1); tr1001_rxstate = RXSTATE_RECEVING; /* Read all incoming syncronization bytes. */ PT_WAIT_WHILE(&rxhandler_pt, incoming_byte == SYNCH1); /* We should receive the second synch byte by now, otherwise we'll restart the protothread. */ if(incoming_byte != SYNCH2) { PT_RESTART(&rxhandler_pt); } /* Reset the CRC. */ rxcrc = 0xffff; /* Read packet header. */ for(tmppos = 0; tmppos < TR1001_HDRLEN; ++tmppos) { /* Wait for the first byte of the packet to arrive. */ PT_YIELD(&rxhandler_pt); /* If the incoming byte isn't a valid Manchester encoded byte, we start again from the beinning. */ if(!me_valid(incoming_byte)) { PT_RESTART(&rxhandler_pt); } rxtmp = me_decode8(incoming_byte); /* Wait for the next byte to arrive. */ PT_YIELD(&rxhandler_pt); if(!me_valid(incoming_byte)) { PT_RESTART(&rxhandler_pt); } /* Put together the two bytes into a single Manchester decoded byte. */ tr1001_rxbuf[tmppos] = (rxtmp << 4) | me_decode8(incoming_byte); /* Calculate the CRC. */ rxcrc = crc16_add(tr1001_rxbuf[tmppos], rxcrc); } /* Since we've got the header, we can grab the length from it. */ tr1001_rxlen = ((((struct tr1001_hdr *)tr1001_rxbuf)->len[0] << 8) + ((struct tr1001_hdr *)tr1001_rxbuf)->len[1]); /* If the length is longer than we can handle, we'll start from the beginning. */ if(tmppos + tr1001_rxlen > sizeof(tr1001_rxbuf)) { PT_RESTART(&rxhandler_pt); } /* Read packet data. */ for(tmppos = 6; tmppos < tr1001_rxlen + TR1001_HDRLEN; ++tmppos) { PT_YIELD(&rxhandler_pt); if(!me_valid(incoming_byte)) { PT_RESTART(&rxhandler_pt); } rxtmp = me_decode8(incoming_byte); PT_YIELD(&rxhandler_pt); if(!me_valid(incoming_byte)) { PT_RESTART(&rxhandler_pt); } tr1001_rxbuf[tmppos] = (rxtmp << 4) | me_decode8(incoming_byte); rxcrc = crc16_add(tr1001_rxbuf[tmppos], rxcrc); } /* Read the frame CRC. */ for(tmppos = 0; tmppos < 4; ++tmppos) { PT_YIELD(&rxhandler_pt); if(!me_valid(incoming_byte)) { PT_RESTART(&rxhandler_pt); } rxcrctmp = (rxcrctmp << 4) | me_decode8(incoming_byte); } if(rxcrctmp == rxcrc) { /* A full packet has been received and the CRC checks out. We'll request the driver to take care of the incoming data. */ tr1001_drv_request_poll(); /* We'll set the receive state flag to signal that a full frame is present in the buffer, and we'll wait until the buffer has been taken care of. */ tr1001_rxstate = RXSTATE_FULL; PT_WAIT_UNTIL(&rxhandler_pt, tr1001_rxstate != RXSTATE_FULL); } } PT_END(&rxhandler_pt);}

Event-based implementation

/* No bytes read, waiting for synch byte. */#define RXSTATE_READY 0/* Second start byte read, waiting for header. */#define RXSTATE_START 1/* Reading packet header, first Manchester encoded byte. */#define RXSTATE_HEADER1 2/* Reading packet header, second Manchester encoded byte. */#define RXSTATE_HEADER2 3/* Reading packet data, first Manchester encoded byte. */#define RXSTATE_DATA1 4/* Reading packet data, second Manchester encoded byte. */#define RXSTATE_DATA2 5/* Receiving CRC16 */#define RXSTATE_CRC1 6#define RXSTATE_CRC2 7#define RXSTATE_CRC3 8#define RXSTATE_CRC4 9/* A full packet has been received. */#define RXSTATE_FULL 10 voidtr1001_rxhandler(unsigned char c){ switch(tr1001_rxstate) { case RXSTATE_READY: if(c == SYNCH1) { tr1001_rxstate = RXSTATE_START; rxpos = 0; } break; case RXSTATE_START: if(c == SYNCH1) { tr1001_rxstate = RXSTATE_START; } else if(c == SYNCH2) { tr1001_rxstate = RXSTATE_HEADER1; rxcrc = 0xffff; } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_HEADER1: if(me_valid(c)) { tr1001_rxbuf[rxpos] = me_decode8(c); tr1001_rxstate = RXSTATE_HEADER2; } else { tr1001_rxstate = RXSTATE_ERROR; } break; case RXSTATE_HEADER2: if(me_valid(c)) { tr1001_rxbuf[rxpos] = (tr1001_rxbuf[rxpos] << 4) | me_decode8(c); rxcrc = crc16_add(tr1001_rxbuf[rxpos], rxcrc); ++rxpos; if(rxpos >= TR1001_HDRLEN) { tr1001_rxlen = ((((struct tr1001_hdr *)tr1001_rxbuf)->len[0] << 8) + ((struct tr1001_hdr *)tr1001_rxbuf)->len[1]); if(rxpos + tr1001_rxlen == sizeof(tr1001_rxbuf)) { tr1001_rxstate = RXSTATE_DATA1; } } else { tr1001_rxstate = RXSTATE_HEADER1; } } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_DATA1: if(me_valid(c)) { tr1001_rxbuf[rxpos] = me_decode8(c); tr1001_rxstate = RXSTATE_DATA2; } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_DATA2: if(me_valid(c)) { tr1001_rxbuf[rxpos] = (tr1001_rxbuf[rxpos] << 4) | me_decode8(c); rxcrc = crc16_add(tr1001_rxbuf[rxpos], rxcrc); ++rxpos; if(rxpos == tr1001_rxlen + TR1001_HDRLEN) { tr1001_rxstate = RXSTATE_CRC1; } else if(rxpos > sizeof(tr1001_rxbuf)) { tr1001_rxstate = RXSTATE_READY; } else { tr1001_rxstate = RXSTATE_DATA1; } } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_CRC1: if(me_valid(c)) { rxcrctmp = me_decode8(c); tr1001_rxstate = RXSTATE_CRC2; } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_CRC2: if(me_valid(c)) { rxcrctmp = (rxcrctmp << 4) | me_decode8(c); tr1001_rxstate = RXSTATE_CRC3; } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_CRC3: if(me_valid(c)) { rxcrctmp = (rxcrctmp << 4) | me_decode8(c); tr1001_rxstate = RXSTATE_CRC4; } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_CRC4: if(me_valid(c)) { rxcrctmp = (rxcrctmp << 4) | me_decode8(c); if(rxcrctmp == rxcrc) { tr1001_rxstate = RXSTATE_FULL; tr1001_drv_request_poll(); } else { tr1001_rxstate = RXSTATE_READY; } } else { tr1001_rxstate = RXSTATE_READY; } break; case RXSTATE_FULL: /* Just drop the incoming byte. */ break; default: /* Just drop the incoming byte. */ tr1001_rxstate = RXSTATE_READY; break; }}
Protothreads - examples (2024)
Top Articles
Latest Posts
Article information

Author: Terrell Hackett

Last Updated:

Views: 6143

Rating: 4.1 / 5 (52 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Terrell Hackett

Birthday: 1992-03-17

Address: Suite 453 459 Gibson Squares, East Adriane, AK 71925-5692

Phone: +21811810803470

Job: Chief Representative

Hobby: Board games, Rock climbing, Ghost hunting, Origami, Kabaddi, Mushroom hunting, Gaming

Introduction: My name is Terrell Hackett, I am a gleaming, brainy, courageous, helpful, healthy, cooperative, graceful person who loves writing and wants to share my knowledge and understanding with you.