How's that interrupt-driven UART coming on ?

12 Nov 2009 . Edited: 16 Nov 2009

Simon - you mentioned that you were working on an interrupt-driven UART a few days ago - I'd just like to underscore my interest in a thing of that nature.

As my camera-based experiment has expanded, I am having more and more trouble keeping up with the serial stream from the camera. I need to read 5k bytes in a chunk, ideally at 115200 baud, and I am finding that any tracing at all during that period causes bytes to be lost.

And, as I am now doing some servo-driving work in a Ticker interrupt, i have other code that needs debugging going on at more or less the same time.

I'm sure I shall muddle through for now, but when you get our interrupt-UART going I'll be very happy to make use of it.

Thanks!
Richard

16 Nov 2009

Hi Richard,

We've just pushed live a library update (v17) that includes a first example of Serial with interrupt support.

To get straight to an example:

For now, we've just added an "attach" function (much like on InterruptIn or Ticker/Timeout). You can attach a function or member function which is called whenever a character is received. For example:

#include "mbed.h"

DigitalOut myled(LED1);
DigitalOut myled2(LED2);
Serial pc(USBTX, USBRX);

void foo() {
    myled = !myled;
    pc.putc(pc.getc() + 1);
}

int main() {
    pc.attach(&foo);
    while(1) {
        myled2 = !myled2;
        wait(0.25);
    }
}

Note that foo() is called by the hardware interrupt, and to "clear" the source of the interrupt you need to read a character (so you don't end up in an endless loop of repeatedly calling the interrupt!).

Whilst I don't think this is quite right yet (e.g. it should probably support tx and rx interrupts to enable easy coding of full s/w buffered UARTs), I'll let you have a play with this now to see how you get on.

To give a hint on a crude buffering system:

volatile char buffer[256];
volatile int ptr = 0;

void foo() {
    buffer[ptr++] = pc.getc();
}

If you want to use this on an existing project, remember to update the library; select it in the file tree and click "Update to latest version".

Thanks,
Simon

16 Nov 2009

Simon - thanks - that's splendid!

I'll go build me a buffered serial class and see how that helps my camera interface.

For what it is worth, I think that a Serial class that already knows how to buffer inbound bytes would be the easiest thing for beginners to get to grips with. I'll go make one and let you know how it gets on.

 

 

Richard

16 Nov 2009

Simon - I have knocked up a quick circular buffer class that sits on top of Serial. I'm aiming to make a drop-in replacement for Serial that just exploits the new interrupt to provide an input buffer.

Sadly, my app dies completly whenever I get an interrupt. I don't really know whether to think that it is a bug of mine or an issue with the method form of your new interrupt.

Could you cast an eye over this, and see if you think the interrupt part ought to work ?

 

Thanks,

Richard

 

#pragma once

class SerialBuffered : public Serial
{
public:
	SerialBuffered( uint16_t bufferSize, PinName rx, PinName tx );
	virtual ~SerialBuffered();
	
	uint8_t getc();

	int readable();
	

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;
    uint16_t  m_contentSize;
    uint16_t m_buffSize;

};
#include "mbed.h"
#include "SerialBuffered.h"
 
Serial loggerSerial(USBTX, USBRX);
 
SerialBuffered::SerialBuffered( uint16_t bufferSize, PinName rx, PinName tx ) : Serial(  rx,  tx ) 
{
    m_buffSize = 0;
    m_contentStart = 0;
    m_contentSize = 0;
   
    
	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 );
    }
    else
    {
        m_buffSize = bufferSize;
    }
}


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

uint8_t SerialBuffered::getc()
{

	while( m_contentSize == 0 )
	{
	  

	    wait_ms( 1 );
    }
    	    
	uint8_t result = m_buff[m_contentStart];
	m_contentStart =  ( m_contentStart + 1 ) % m_buffSize;
	m_contentSize --;

    return result;	
}


int SerialBuffered::readable()
{
    return  m_contentSize > 0  ;
}

void SerialBuffered::handleInterrupt()
{
    
    while( Serial::readable())
    {
        if( m_contentSize >= m_buffSize )
        {
            loggerSerial.printf("SerialBuffered - buffer overrun, data lost!\r\n" );

        }
        else
        {
            uint16_t offset = ( m_contentStart + m_contentSize ) % m_buffSize;
            m_buff[ offset ] = Serial::getc();
            m_contentSize ++;
           
        }
    }
}

16 Nov 2009

Can you publish it instead, and paste the link back in the forum. Saves anyone who might want to tackle this from having to reconstruct it all from a forum post...

16 Nov 2009 . Edited: 16 Nov 2009

Sure - I've built a tiny demo app, too, which will reproduce the problem. Just join pins 13 & 14 together, and run this:

http://mbed.org/users/jarkman/published/0b643bccefa99bb2c293043e854141ef/SerialBufferedDemo.zip

Thanks!

R.

16 Nov 2009

Hi Richard,

Perfect, much easier! I could reproduce the hardware + software in 30 seconds!

The bug is my end - it works with UART0 (the one over USB, and no surprise, the one I sanity checked on), but not any of the others. I've tracked it down, and will post a library update with the fix when I get the chance.

Simon

16 Nov 2009

Simon - thanks - glad to hear I hadn't gone mad... :-)

 

I had another question for you while I was writing that. The getc and handleInterrupt functions in that class are potentially overlapping, if the interrupt occurs while getc is running, and that will lead to peril.

 

What (if anything) do we have available for synchronisation in the mbed world ? Or should I roll in some flags of my own for that ?

 

Thanks,


Richard

16 Nov 2009 . Edited: 16 Nov 2009

Here's a simple semaphore class that can be used to control access to shared resources. It compiles but I haven't tested it.

class Semaphore
{
  enum { SemFree, SemTaken };
  // semaphore value
  int s;  

public:
  // constructor
  Semaphore(): s(SemFree) {};
  
  // try to take the semaphore and return success
  // by default block until succeeded
  bool take(bool block = true)
  {
    int oldval;
#if defined(TARGET_LPC1768) // on Cortex-M3 we can use ldrex/strex
    do {
      // read the semaphore value
      oldval = __ldrex(&s);
      // loop again if it is locked and we are blocking
      // or setting it with strex failed
    }
    while ( (block && oldval == SemTaken) || __strex(SemTaken, &s) != 0 );
    if ( !block ) __clrex(); // clear exclusive lock set by ldrex
#else // on arm7 there's only swp
    do {
      // swp sets the pointed data to the given value and returns the previous one
      oldval = __swp(SemTaken, &s);
      // if blocking, loop until the previous value becomes 0
      // which would mean we have successfully taken the lock
    }
    while (block && oldval == SemTaken);
#endif
    return oldval == SemFree;
  }
  
  // release the semaphore
  void release()
  {
    s = SemFree;
  }
};

Edit: added __clrex() necessary to clear lock after ldrex on CM3.

17 Nov 2009

Hi Richard,

I've just updated the library again to make the fixes I identified.

Then, imported your project, added the wire, clicked "Update Library..." and got this:

Seems like your code works great :)

Btw, I noticed the BufferedSerial constructor argument names are around the wrong way (it is in there as rx, tx). It doesn't matter, as it is switched in both your constructor and your initialisation of Serial, but really should be tx, rx in both places to be correct/avoid confusion.

Simon

17 Nov 2009

Hurrah! Thanks for the quick fix!

I'll fix it up, add Igor's semaphore, and put it in the camera code. Once it has proved itself I'll have a go at a Cookbook entry.

 

Richard

18 Nov 2009

That all seems to work now.

In the end, I rejigged my counters to avoid requiring semaphores. the final product, with a little demo app, is here:

http://mbed.org/users/jarkman/published/80cd523739507c8bade52dfce149d900/SerialBufferedDemo.zip

Next, the Cookbook. Though maybe not tonight.

 

R.

25 Jan 2010

Richard,

 

You're app has been quite handy, as I'm currently attempting to implement more standard linux "read" and "write" like functions for the serial port.  I've had troubles with mixing Serial::printf() and the serial interrupts as I've gone into over on this thread.  So using your SerialBuffered class as a base, I thought it might be good to recreate what you did for serial transmitting.  Basically I've added a writeBytes() function similar to your readBytes() function that uses a ticker instead of the serial interrupt.  I set it up to run at 100Hz (adjustable) ticker rate which checks if there are any bytes in another circular buffer and if so send them on their way.   This allows for a non blocking transmit function which may be useful (especially at lower baud rates), but of course there is some overhead and potential latency in when you send the bytes and when they're actually sent etc...

I'm mainly doing this to get functionality similar to printf but implementing it using putc's so I don't get that odd system hang behavior as described in the other thread...  I've almost got it figured out, but need to do some more testing so I can get your test app working without any bad bytes etc...

 

Anyway, thanks for your handy code!

-John

09 Feb 2010
Igor Skochinsky wrote:
Here's a simple semaphore class that can be used to control access to shared resources. It compiles but I haven't tested it.

Hi

I'm trying to understand how a semaphore class can be used in the absence of a RTOS. For example, Richard was concerned that getc could overlap with the receive interrupt, so in the getc method one could wrap access to buffer using a take/release of the semaphore. In the interrupt handler you use take/release on the same semaphore to limit buffer access.

My concern is as follows. You call getc from your main() method, and it takes the semaphore (but doesn't get as far as touching the buffer). Immediately the receive interrupt comes in, and you want to store the character, but buffer access is blocked because the semaphore is already taken. Since you are now in the interrupt, the code in main() will not complete, so you have a deadlock. The only way to avoid the deadlock is to have some way of rescheduling the main() code to run whilst the interrupt handler is blocked (the kind of thing an RTOS does). I'm not sure using a non-blocking semaphore in the interrupt handler will help, because then what do you do with the received character?

Is the way to handle this to use a more traditional method of enabling and disabling interrupts around the calls to getc?

Thanks
Daniel

 

 

08 Sep 2010

Dear Mr. Daniel

Hi.

http://mbed.org/forum/mbed/topic/320/ is good information for you maybe.

I had same question few weeks ago.

08 Sep 2010
user avatar Shinichiro Nakamura wrote:
http://mbed.org/forum/mbed/topic/320/ is good information for you maybe.

Dear Shinichiro Nakamura

My contention was that you couldn't use a semaphore without an RTOS. My own code is littered with critical sections as result.

Did you manage to use some semaphore code without RTOS (or other scheduler) - that I am interested in conceptually.

Thanks
Daniel