SerialBuffered, how to stop or destroy the buffering process

19 Oct 2010

Hi !

I have some trouble in the serial stream decoding in my datalogger, i use the Richard Sewell's SerialBuffered code, and it's much better, thank you Richard.

I could initialize the object and use all functions, now my serial stream decoding routine works very well, but how to "stop" the SerialBuffer interrupt ? my datalogger catch a serial stream, after decoding i need to do lots of thing in the decoded data, send the data on a SdCard, on the net, ...

With the original Richard's code, the buffer continue to grown and be full very quickly if i stop to read the serial, and the code never go out from the interrupt (SerialBuffered::handleInterrupt())

I would like to start/stop the "buffering process" when i want or create/destroy the SerialBuffered object when i need to read my serial stream.

Richard use attach( this, &SerialBuffered::handleInterrupt ); in the class constructor, is it possible to "detach", or destroy the object and to be sure the interrupt is stopped ?

I'm not very comfortable with mBed...

If you have an idea.

Thank you.

Chris.


19 Oct 2010

Hi Chris,

There's a number of ways to do what you want. The simplest is to have a "flag" (bool) that, when true, the interrupt service routine puts the character into the buffer, when false, it doesn't. You can then switch the boolean flag as you like.

However, the method I prefer is, well, do nothing. So what if the buffer continues to fill. It's a fixed fix and the all the buffer logic can do is eirther overflow or wrap. The important thing using this method however is to always flush the buffers before you start capturing to make sure you start in a good known state. An example of this is my Nexstar telescope mount. When you switch it on it has a habbit of sending a load of junk characters down the serial line. My interrupts capture them all. But, before I ever try and use the comms channel to send and receive command packets I ensure I clean (flush) the buffers.

Other methods exist in sample code you can find on my profile page. However, if you are not that comfortable yet with the Mbed then one of the two above methods should work for you.

regards,

--Andy

20 Oct 2010

Hi Andy,

 

Thank you for your answer.

I will try this evening your idea.

I was find you BufferedSerial class, but i see your "warning" about use .attach() method, and i use it in my datalogger code...

Thank you again.

Chris.

26 Oct 2010 . Edited: 26 Oct 2010

Hi,

Here the modified code to start/stop the buffering process, after 1 week of test "on field" all is ok.

SerialBuffered.cpp

 

SerialBuffered::SerialBuffered( size_t bufferSize, PinName tx, PinName rx ) : Serial(  tx,  rx ) 
{
    m_buffSize = 0;
    m_contentStart = 0;
    m_contentEnd = 0;
    m_timeout = 1.0;
    flagStopSerialBuffered = true;
   
    attach( this, &SerialBuffered::handleInterrupt );
    
    m_buff = (uint8_t *) malloc( bufferSize );
    if( m_buff == NULL )
    {
        //loggerSerial.printf("SerialBuffered - failed to alloc buffer size %d\r\n", (int) bufferSize );
        //debugNewLine("SerialBuffered - failed to alloc buffer\n");
    }
    else
    {
        m_buffSize = bufferSize;
    }
}

void SerialBuffered::stop()
{
    flagStopSerialBuffered = false;
}
void SerialBuffered::start()
{
   flagStopSerialBuffered = true;
}
void SerialBuffered::flush()
{
    m_contentEnd = 0;
    m_contentStart = 0;   
}
   

SerialBuffered::~SerialBuffered()
{
    if( m_buff )
        free( m_buff );
}

void SerialBuffered::setTimeout( float seconds )
{
    m_timeout = seconds;
}
    
size_t SerialBuffered::readBytes( uint8_t *bytes, size_t requested )
{
    int i = 0;

    for( ; i < requested; )
    {
        int c = getc();
        if( c < 0 )
            break;
        bytes[i] = c;
        i++;
    }
    
    return i;
        
}


int SerialBuffered::getc()
{
    m_timer.reset();
    m_timer.start();
    while( m_contentStart == m_contentEnd )
    {
      

        wait_ms( 1 );
        if( m_timeout > 0 &&  m_timer.read() > m_timeout )
            return EOF;
    }

    m_timer.stop();
   
    uint8_t result = m_buff[m_contentStart++];
    m_contentStart =  m_contentStart % m_buffSize;

   
    return result;    
}

int SerialBuffered::readable()
{
    return m_contentStart != m_contentEnd ;
}

void SerialBuffered::handleInterrupt()
{
    if (flagStopSerialBuffered == true) //la reception est active
    {
        while(Serial::readable())
        {
            if( m_contentStart == (m_contentEnd +1) % m_buffSize)
            {
                //debugNewLine("SerialBuffered - buffer overrun, data lost!\n");                
                Serial::getc();                        
            }
            else
            {          
                m_buff[ m_contentEnd ++ ] = Serial::getc();
                m_contentEnd = m_contentEnd % m_buffSize;           
            }
        }
     }
     else
     {
       //Received caracter need to be read !
       Serial::getc()
     } 
}

SerialBuffered.h

 

#pragma once

// This is a buffered serial reading class, using the serial interrupt introduced in mbed library version 18 on 17/11/09

// In the simplest case, construct it with a buffer size at least equal to the largest message you 
// expect your program to receive in one go.

class SerialBuffered : public Serial
{
public:
    SerialBuffered( size_t bufferSize, PinName tx, PinName rx );
    virtual ~SerialBuffered();
    
    int getc();     // will block till the next character turns up, or return -1 if there is a timeout
    
    int readable(); // returns 1 if there is a character available to read, 0 otherwise
    
    void setTimeout( float seconds );    // maximum time in seconds that getc() should block 
                                         // while waiting for a character
                                         // Pass -1 to disable the timeout.
    
    size_t readBytes( uint8_t *bytes, size_t requested );    // read requested bytes into a buffer, 
                                                             // return number actually read, 
                                                             // which may be less than requested if there has been a timeout
    void stop();
    void start();
    void flush();
    

private:
    
    void handleInterrupt();
   
    uint8_t *m_buff;            // points at a circular buffer, containing data from m_contentStart, for m_contentSize bytes, wrapping when you get to the end
    uint16_t  m_contentStart;   // index of first bytes of content
    uint16_t  m_contentEnd;     // index of bytes after last byte of content
    uint16_t m_buffSize;
    float m_timeout;
    Timer m_timer;
    bool flagStopSerialBuffered;
   
};

Init in main.cpp for example :

 

 

    SerialBuffered *serialStream = new SerialBuffered(100, p13, p14);       
    serialStream ->baud( 1200 );
    serialStream ->format(7,serialStream->Even,1); 
    serialStream ->setTimeout( 1 );
    serialStream ->stop();

To start the buffer process, use the serial and stop the buffering process :

 

 

serialStream->flush();
serialStream->start();
//Here your "serial" parsing code...                            
serialStream->stop();

Thank you Andy for your idea.

 

Chris.

05 Dec 2011

IMO you could also attach another IRQ handling callback e.g. void HandleIRQNoBuffering() which just calls Serial::getc()