Protothreads in C++

1k Views Asked by At

Hi i am working on embedded system which controls a elevator through serial bus. Every time i send a message (packet contains adress,data length, data, crc) i need to wait for elevator response, which is represented by acknowledgement packet.

ACK is set every time when I receive packet from elevator. Receiving messages is done through Interrupt. ACK packet looks like:

0xA0,0x00,0x00,0x00,0x00

and when it comes I set ACK to true

Here is my send function:

bool TransferService::send(char recAddr,char dataLength, char *data){
     pc.putc(startByte);
     pc.putc(recAddr);
     pc.putc(controllerAddress);
     pc.putc(dataLength);
     for (int i = 0; i < dataLength; i++) {
       pc.putc(data[i]);
     }
     pc.putc(getCrc(recAddr,controllerAddress,data, dataLength));
     _messageReceived = false;
     timer.reset();
     timer.start();
     ACK = false;
     do {
       if (ACK) {
         break;
       }
     } while(timer.read_ms()<=15);
     timer.stop();
     if (!ACK) {
       send(recAddr,dataLength,data);
     }
}

This is only try, it not work.

bool TransferService::send(char recAddr,char dataLength, char *data){
PT_BEGIN();
  timer.reset();
  timer.start();
  do {
    pc.putc(startByte);
    pc.putc(recAddr);
    pc.putc(controllerAddress);
    pc.putc(dataLength);
    for (int i = 0; i < dataLength; i++) {
      pc.putc(data[i]);
    }
    pc.putc(getCrc(recAddr,controllerAddress,data, dataLength));
    _messageReceived = false;
    PT_WAIT_UNTIL(!timer.read_ms() <=10 || ACK);
  } while(timer.read_ms() <=10);
  PT_END();
}

My question is how to make first function work correctly using protothreads.

1

There are 1 best solutions below

1
On

The structure of a thread function in Adam Dunkels' implementation of Protothreads is quite clear, and your function clearly does not follow it.

A Protothread thread function must return int and normally has the signature:

int threadfunction( struct pt* pt ) ;

and must be defined using the PT_THREAD macro thus:

PT_THREAD( threadfunction( struct pt* pt ) )
{
    PT_BEGIN(pt) ;
    // thread body here
    PT_END(pt) ;
}

From the Protothread documentation...

A protothread function must always return an integer, but must never explicitly return - returning is performed inside the protothread statements.

Looking at the definition of PT_THREAD I can see nothing that would prevent its use with a C++ member function, or the use of additional arguments other than pt, in which case, the following is closer to correct:

PT_THREAD( TransferService::send( struct pt* pt, char recAddr, char dataLength, char *data )
{
    PT_BEGIN( pt );
    timer.reset();
    timer.start();

    pc.putc( startByte );
    pc.putc( recAddr );
    pc.putc( controllerAddress );
    pc.putc( dataLength );
    for( int i = 0; i < dataLength; i++ )
    {
        pc.putc( data[i] );
    }
    pc.putc( getCrc( recAddr, controllerAddress, data, dataLength ) );
    _messageReceived = false;

    PT_WAIT_UNTIL( pt, timer.read_ms() > 10 || ACK );

    PT_END();
}

In your attempt you had both a do-while loop and a PT_WAIT_UNTIL, but the WAIT makes the loop unnecessary. It is not in fact necessary to pass the pt argument, the required struct pt might be a class member or even a global (although that would be ill-advised).

Note that the above follows the pattern evident in the question, but would be an unusual design pattern; most often a thread runs indefinitely rather than for a single transaction. Without knowledge of your entire application, I would suggest that the thread should operate at a higher level than the single transactionsend() function, such that you have a "sender" thread that can repeatedly perform send operations without exiting as in the following outline (i.e. not complete or "real" code, and with a great many assumptions):

// Constructor...
TransferService::TransferService()
{
    PT_INIT( &m_pt ) ;  // Where thread state m_pt 
                        // is a member variable of
                        // type struct pt (or just pt 
                        // since this is C++)
    ...
}

// Thread function
PT_THREAD(TransferService::senderThread() )
{
  PT_BEGIN(&m_pt);     
  for(;;)
  {
      PT_WAIT_UNTIL( &m_pt, ready_to_send ) ;

      timer.reset();
      timer.start();
      send( recAddr, dataLength, data ) ;

      PT_WAIT_UNTIL( &m_pt, timer.read_ms() > 10 || ACK );
  }
  PT_END(pt);
}

// Single send transaction function
bool TransferService::send( char recAddr, char dataLength, char *data )
{
    pc.putc( startByte );
    pc.putc( recAddr );
    pc.putc( controllerAddress );
    pc.putc( dataLength );
    for( int i = 0; i < dataLength; i++ )
    {
        pc.putc( data[i] );
    }
    pc.putc( getCrc( recAddr, controllerAddress, data, dataLength ) );
    _messageReceived = false;
}

Note that due to the manner in which the PT_... API works, the PT_WAIT_UNTIL must be in the same function as the PT_BEGIN and PT_END, hence moving the timer wait in the code above.