Buffered Serial Port Driver for RTOS

Dependents:   nucleo_cannonball PiballNeoController

Buffered Serial Port Driver for RTOS

  • ISR driven, ring buffered IO operation
  • IO operations are idle waiting, don't waste time in RTOS :D
  • Can use external buffers
  • Based on mbed RawSerial

Example

SerialDriver Example

#include "SerialDriver.h"

SerialDriver pc(USBTX, USBRX);

int main()
{
    // setup serial port
    pc.baud(9600);
    
    // print some text
    pc.puts("This is just a string.\r\n");
    pc.printf("But this is a %s with integer %i and float %f.\r\n", "formatted text", 123, 0.456f);
    
    // now lets behave like a null modem 
    while(1)
       pc.putc(pc.getc());
}

Look at the API Documentation for more Examples.

Dependencies

Import librarymbed

The official Mbed 2 C/C++ SDK provides the software platform and libraries to build your applications.

Import librarymbed-rtos

Official mbed Real Time Operating System based on the RTX implementation of the CMSIS-RTOS API open standard.

If you find a bug, please help me to fix it. Send me a message. You can help me a lot: Write a demo program that causes the bug reproducible.

Files at this revision

API Documentation at this revision

Comitter:
BlazeX
Date:
Wed Jan 14 16:30:14 2015 +0000
Child:
1:1464146bd7fb
Commit message:
Release :)

Changed in this revision

Examples/Example_Blocking.cpp Show annotated file Show diff for this revision Revisions of this file
Examples/Example_Bridge.cpp Show annotated file Show diff for this revision Revisions of this file
Examples/Example_Nullmodem.cpp Show annotated file Show diff for this revision Revisions of this file
Examples/Example_printf.cpp Show annotated file Show diff for this revision Revisions of this file
SerialDriver.cpp Show annotated file Show diff for this revision Revisions of this file
SerialDriver.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Examples/Example_Blocking.cpp	Wed Jan 14 16:30:14 2015 +0000
@@ -0,0 +1,67 @@
+/// @file Example_Blocking.cpp
+/// @brief Test blocking write / read
+/// 
+/// - Uses SerialDriver with USBTX and USBRX.
+/// - Has much too small buffers, to how far can it get?
+/// 
+/// - Testing how much bytes were transmitted, with too small TX buffer and forced non blocking
+/// - Waiting till 10 bytes are received
+/// - LED4 indicates parallel thread working
+/// 
+
+#if 0
+
+#include "mbed.h"
+#include "SerialDriver.h"
+
+SerialDriver pc(USBTX, USBRX, 4, 32);
+
+// This thread is running in parallel
+DigitalOut led4(LED4);
+void parallel(void const * argument)
+{
+    while(1)
+    {
+        Thread::wait(20);
+        led4= !led4;
+    }
+}
+
+int main()
+{
+    // Start the other thread
+    Thread parallelTask(&parallel);
+    
+    
+    const char * completeText= "This is a complete text. How much will you receive?";
+    const int completeTextLength= strlen(completeText);
+    int writtenBytes;
+    
+    
+    const int readBufferLength= 10;
+    unsigned char readBuffer[readBufferLength];
+    int receivedBytes= 0;
+    
+    while(1)
+    {
+        // write non blocking, how much get transmitted?
+        writtenBytes= pc.write((const unsigned char*)completeText, completeTextLength, false);
+        
+        // now print the result
+        pc.printf("\r\nOnly %i of %i bytes were transmitted using non blocking write.\r\n", writtenBytes, completeTextLength);
+        
+        
+        // wait for 10 bytes
+        pc.printf("I wait for my 10 bytes. Send them!\r\n", writtenBytes, completeTextLength);
+        receivedBytes+= pc.read(readBuffer, readBufferLength);
+        
+        // now print result
+        pc.printf("Received %i bytes since start.\r\n\r\n", receivedBytes);
+        
+        
+        // wait a bit
+        Thread::wait(1000);
+    }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Examples/Example_Bridge.cpp	Wed Jan 14 16:30:14 2015 +0000
@@ -0,0 +1,62 @@
+/// @file Example_Bridge.cpp
+/// @brief Forwarding every byte from USB to uart and vice versa
+/// 
+/// - Uses SerialDriver with USBTX and USBRX.
+/// - Uses SerialDriver with p13 and p14.
+/// 
+/// - LED1 indicates forwarding pc to uart works
+/// - LED2 indicates forwarding uart to pc works
+/// - LED4 indicates a parallel thread running
+/// 
+/// - connect p13 with p14 to get a hardware null modem ;)
+
+#if 0
+
+#include "mbed.h"
+#include "SerialDriver.h"
+
+SerialDriver pc(USBTX, USBRX);
+SerialDriver uart(p13, p14);
+DigitalOut led1(LED1), led2(LED2), led4(LED4);
+
+// This thread forwards from pc to uart
+void forwardPc(void const * argument)
+{
+    // Sometimes You're the Hammer, 
+    while(1)
+    {    
+        uart.putc(pc.getc());
+        led1= !led1;    
+    }
+}
+
+// This thread forwards from uart to pc
+void forwardUart(void const * argument)
+{
+    // Sometimes You're the Nail
+    while(1)
+    {      
+        pc.putc(uart.getc());
+        led2= !led2;    
+    }
+}
+
+int main()
+{
+    // setup serial ports
+    pc.baud(9600);
+    uart.baud(38400);
+    
+    // Start the forwarding threads
+    Thread forwardPcThread(&forwardPc);
+    Thread forwardUartThread(&forwardUart);
+    
+    // Do something else now  
+    while(1)
+    {
+        Thread::wait(20);
+        led4= !led4;
+    }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Examples/Example_Nullmodem.cpp	Wed Jan 14 16:30:14 2015 +0000
@@ -0,0 +1,50 @@
+/// @file Example_Nullmodem.cpp
+/// @brief USB null modem
+/// 
+/// - Uses SerialDriver with USBTX and USBRX.
+/// - Send back every received byte.
+/// 
+/// - LED1 indicates waiting for @ref SerialDriver::getc
+/// - LED2 indicates waiting for @ref SerialDriver::putc
+/// - LED4 indicates a parallel thread running
+
+#if 0
+
+#include "mbed.h"
+#include "SerialDriver.h"
+
+SerialDriver pc(USBTX, USBRX);
+DigitalOut led1(LED1), led2(LED2), led4(LED4);
+
+// This thread is emulating a null modem
+void nullmodem(void const * argument)
+{
+    pc.baud(9600);
+    
+    int c;
+    while(1)
+    {
+        led1= 1;
+        c= pc.getc();
+        led1= 0;
+        
+        led2= 1;
+        pc.putc(c);
+        led2= 0;
+    }
+}
+
+int main()
+{
+    // Start the null modem
+    Thread nullmodemTask(&nullmodem);
+    
+    // Do something else now  
+    while(1)
+    {
+        Thread::wait(20);
+        led4= !led4;
+    }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Examples/Example_printf.cpp	Wed Jan 14 16:30:14 2015 +0000
@@ -0,0 +1,48 @@
+/// @file Example_printf.cpp
+/// @brief Formatted output
+/// 
+/// - Uses SerialDriver with USBTX and USBRX.
+/// - Has much too small buffers, but printf is blocking, so it does not matter
+/// 
+/// - The terminal will be flooded with text
+/// - LED4 indicates parallel thread working
+/// 
+
+#if 0
+
+#include "mbed.h"
+#include "SerialDriver.h"
+
+// only 4 byte of software ring buffer?
+// No problem! SerialDriver uses idle blocking calls :D
+SerialDriver pc(USBTX, USBRX, 4, 4);
+
+// This thread is running in parallel
+DigitalOut led4(LED4);
+void parallel(void const * argument)
+{
+    while(1)
+    {
+        Thread::wait(20);
+        led4= !led4;
+    }
+}
+
+int main()
+{
+    // Start the other thread
+    Thread parallelTask(&parallel);
+    
+    float f= 0.0f;    
+    while(1)
+    {
+        // unformatted text
+        pc.puts("Hi! this uses puts.\r\n");
+        
+        // formatted text
+        pc.printf("And this is formatted. Here is the sin(%f)=%f.\r\n", f, sinf(f));
+        f+= 0.25f;
+    }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SerialDriver.cpp	Wed Jan 14 16:30:14 2015 +0000
@@ -0,0 +1,215 @@
+#include "SerialDriver.h"
+
+SerialDriver::SerialDriver(PinName txPin, PinName rxPin, int txBufferLength_, int rxBufferLength_, unsigned char * txBuffer_, unsigned char * rxBuffer_)
+    : SerialBase(txPin, rxPin), semTxBufferFull(0), semRxBufferEmpty(0)
+{    
+    // check buffer length
+    txBufferLength= txBufferLength_;
+    if(txBufferLength <= 1)
+        error("TX buffer length must be > 1 !");
+        
+    rxBufferLength= rxBufferLength_;
+    if(rxBufferLength <= 1)
+        error("RX buffer length must be > 1 !");
+    
+    // take or allocate buffer
+    txBuffer= txBuffer_;
+    if(txBuffer == NULL)
+    {
+        txBuffer= new unsigned char[txBufferLength];
+        if(txBuffer == NULL)
+            error("Cannot allocate TX buffer!");
+    }
+    
+    rxBuffer= rxBuffer_;
+    if(rxBuffer == NULL)
+    {
+        rxBuffer= new unsigned char[rxBufferLength];
+        if(rxBuffer == NULL)
+            error("Cannot allocate RX buffer!");
+    }
+        
+    
+    // reset cursors
+    txIn= txOut= 0;
+    rxIn= rxOut= 0;
+    txCount= rxCount= 0;
+    
+    // attach interrupt routines
+    attach(this, &SerialDriver::onTxIrq, TxIrq);
+    attach(this, &SerialDriver::onRxIrq, RxIrq);
+    
+}
+
+int SerialDriver::putc(int c, unsigned int timeoutMs)
+{
+    // critical section, isr could modify cursors
+    disableTxInterrupt();
+    
+    if(isTxBufferFull())
+    {
+        // wait for free space
+        while(semTxBufferFull.wait(0) > 0);    // clear semaphore
+        enableTxInterrupt();
+        
+        // let isr work
+        semTxBufferFull.wait(timeoutMs);
+        
+        disableTxInterrupt();
+        if(isTxBufferFull()) // still full? drop byte!
+        {
+            enableTxInterrupt();
+            return 0;
+        }
+    }
+    
+    // write this byte to tx buffer
+    txBuffer[txIn]= (unsigned char)c;
+    txIn= (txIn+1) % txBufferLength;
+    txCount++;
+    
+    // its over, isr can come
+    enableTxInterrupt();
+        
+    // Let's write (isr will check writeability itself)
+    onTxIrq();
+    
+    return 1;
+}
+
+void SerialDriver::onTxIrq()
+{
+    // prevent fire another TxIrq now
+    disableTxInterrupt();
+    
+    // write as long as you can
+    bool wasFull= isTxBufferFull();
+    while(SerialBase::writeable() && !isTxBufferEmtpy())
+    {
+        // take byte from tx buffer and put it out
+        SerialBase::_base_putc(txBuffer[txOut]);
+        txOut= (txOut+1) % txBufferLength;
+        txCount--;
+    }
+    
+    if(wasFull && !isTxBufferFull())   // more bytes can come
+        semTxBufferFull.release();
+    
+    // ok, let's wait for next writability
+    enableTxInterrupt();
+}
+
+
+int SerialDriver::getc(unsigned int timeoutMs)
+{    
+    // Let's read (isr will check readability itself)
+    onRxIrq();
+    
+    // critical section, isr could modify cursors
+    disableRxInterrupt();
+    
+    if(isRxBufferEmpty())
+    {
+        // wait for new byte
+        while(semRxBufferEmpty.wait(0) > 0);    // clear semaphore
+        enableRxInterrupt();
+        
+        // let isr work
+        semRxBufferEmpty.wait(timeoutMs);
+        
+        disableRxInterrupt();
+        if(isRxBufferEmpty()) // still empty? nothing received!
+        {
+            enableRxInterrupt();
+            return -1;
+        }
+    }
+    
+    // get byte from rx buffer
+    int c= (int)rxBuffer[rxOut];
+    rxOut= (rxOut+1) % rxBufferLength;
+    rxCount--;
+    
+    // its over, isr can come
+    enableRxInterrupt();
+    
+    return c;
+}
+
+void SerialDriver::onRxIrq()
+{
+    // prevent fire another RxIrq now
+    disableRxInterrupt();
+    
+    // read as long as you can
+    bool wasEmpty= isRxBufferEmpty();
+    while(SerialBase::readable())
+    {
+        // get byte and store it to the RX buffer
+        int c= SerialBase::_base_getc();
+        if(!isRxBufferFull())
+        {
+            rxBuffer[rxIn]= (unsigned char)c;
+            rxIn= (rxIn+1) % rxBufferLength;
+            rxCount++;
+        }   // else drop byte :(
+    }
+    
+    if(wasEmpty && !isRxBufferEmpty())   // more bytes can go
+        semRxBufferEmpty.release();
+    
+    // ok, let's wait for next readability
+    enableRxInterrupt();
+}
+
+
+int SerialDriver::write(const unsigned char * buffer, const unsigned int length, bool block)
+{
+    // try to put all bytes
+    for(int i= 0; i < length; i++)
+        if(!putc(buffer[i], block ? osWaitForever : 0))
+            return i; // putc failed, but already put i bytes
+    
+    return length;  // put all bytes
+}
+    
+int SerialDriver::read(unsigned char * buffer, const unsigned int length, bool block)
+{
+    // try to get all bytes
+    int c;
+    for(int i= 0; i < length; i++)
+    {
+        c= getc(block ? osWaitForever : 0);
+        if(c < 0)
+            return i; // getc failed, but already got i bytes
+        buffer[i]= (unsigned char)c;
+    }
+    
+    return length;  // got all bytes
+}
+
+
+int SerialDriver::puts(const char * str, bool block)
+{
+    // the same as write, but get length from strlen
+    const int len= strlen(str);
+    return write((const unsigned char *)str, len, block);
+}
+
+int SerialDriver::printf(const char * format, ...)
+{
+    // Parts of this are copied from mbed RawSerial ;)
+    std::va_list arg;
+    va_start(arg, format);
+    
+    int length= vsnprintf(NULL, 0, format, arg);
+    char *temp = new char[length + 1];
+    vsprintf(temp, format, arg);
+    puts(temp, true);
+    delete[] temp;
+    
+    va_end(arg);
+    return length;
+} 
+
+// for XTN
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SerialDriver.h	Wed Jan 14 16:30:14 2015 +0000
@@ -0,0 +1,173 @@
+/// @file SerialDriver.h
+/// @brief RTOS compatible buffered Serial port driver
+/// 
+/// Examples:
+/// - @ref Example_printf.cpp
+/// - @ref Example_Nullmodem.cpp
+/// - @ref Example_Bridge.cpp
+/// - @ref Example_Blocking.cpp
+/// 
+/// 
+/// Dependencies:
+/// - cstdarg
+/// @see https://developer.mbed.org/users/mbed_official/code/mbed-rtos/
+/// @see https://developer.mbed.org/users/mbed_official/code/mbed/
+
+#pragma once
+
+#include "mbed.h"
+#include "rtos.h"
+#include <cstdarg>
+
+
+/// @class SerialDriver
+/// @brief RTOS compatible buffered Serial port driver
+/// 
+/// - Based on SerialBase.
+/// - Can use external buffers.
+/// - ISR driven, ring buffered IO operation
+/// - Can Replace mbed RawSerial
+/// - IO operations are idle waiting, don't waste time in RTOS :D
+/// - Do not use attach methods for TxIrq or RxIrq! They are already in use.
+class SerialDriver : public SerialBase
+{
+protected:
+    // ring buffered rx/tx
+    unsigned char * txBuffer;
+    unsigned char * rxBuffer;
+    int txBufferLength, rxBufferLength;
+    
+    // ring buffer cursors
+    volatile int txIn, txOut;
+    volatile int rxIn, rxOut;
+    volatile int txCount, rxCount;
+    
+    // semaphores for timeout (used as signals)
+    Semaphore semTxBufferFull;   // used by putc to wait
+    Semaphore semRxBufferEmpty;  // used by getc to wait
+    
+    
+public:
+    /// @brief Prepares ring buffer and irq
+    /// 
+    /// If no buffer was set, the buffer gets allocated.
+    /// @param txPin TX PinName, e.g. USBTX
+    /// @param rxPin RX PinName, e.g. USBRX
+    /// @param txBufferLength_ size of TX buffer. Must be > 1!
+    /// @param rxBufferLength_ size of RX buffer. Must be > 1!
+    /// @param txBuffer_ TX buffer, if NULL, the buffer will be allocated
+    /// @param rxBuffer_ RX buffer, if NULL, the buffer will be allocated
+    SerialDriver(PinName txPin, PinName rxPin, int txBufferLength_= 256, int rxBufferLength_= 256, unsigned char * txBuffer_= NULL, unsigned char * rxBuffer_= NULL);
+    
+    
+    ////////////////////////////////////////////////////////////////
+    // Basic IO Operation
+
+    /// @brief Put a byte to the TX buffer
+    /// 
+    /// If the TX buffer is full, it waits the defined timeout.
+    /// Drops the byte, if TX buffer is still full after timeout.
+    /// @param c The byte to write
+    /// @param timeoutMs give TX buffer time to get writeable.
+    /// @return 1 - success, 0 - if TX Buffer was full all the time
+    int putc(int c, unsigned int timeoutMs= osWaitForever);
+
+    
+    /// @brief Get a byte from the RX buffer
+    /// 
+    /// If the RX buffer is empty, it waits the defined timeout.
+    /// @param timeoutMs give RX buffer time to get readable.
+    /// @return next byte from RX buffer or -1 after timeout.
+    int getc(unsigned int timeoutMs= osWaitForever);    
+    
+    
+protected:
+    ////////////////////////////////////////////////////////////////
+    // Interrupts
+    
+    // TX: Move data from txBuffer to SerialBase::putc
+    void onTxIrq(); // serial base port now writeable, lets put some bytes
+    
+    // RX: Move data from SerialBase::getc to rxBuffer
+    void onRxIrq(); // serial base port now readable, lets get some bytes
+    
+    // Enable / Disable
+    inline void disableTxInterrupt()   {    serial_irq_set(&_serial, (SerialIrq)TxIrq, 0);    }
+    inline void enableTxInterrupt()    {    serial_irq_set(&_serial, (SerialIrq)TxIrq, 1);    }
+    
+    inline void disableRxInterrupt()   {    serial_irq_set(&_serial, (SerialIrq)RxIrq, 0);    }
+    inline void enableRxInterrupt()    {    serial_irq_set(&_serial, (SerialIrq)RxIrq, 1);    }    
+    
+
+public:
+    ////////////////////////////////////////////////////////////////
+    // Extended IO Operation
+    
+    /// @brief write a buck of bytes
+    /// 
+    /// No timeout! To block, or not to block. That is the question.
+    /// @param buffer buck of bytes
+    /// @param length write how much bytes?
+    /// @param block idle wait for every @ref putc to complete
+    /// @return written bytes. For non block write it could be < length!
+    int write(const unsigned char * buffer, const unsigned int length, bool block= true);
+    
+    /// @brief read a buck of bytes
+    /// 
+    /// No timeout! To block, or not to block. That is the question.
+    /// @param buffer buck of bytes
+    /// @param length read how much bytes?
+    /// @param block idle wait for every @ref getc to complete
+    /// @return read bytes. For non block read it could be < length!
+    int read(unsigned char * buffer, const unsigned int length, bool block= true);
+    
+    
+    /// @brief Write a string (without terminating null)
+    /// 
+    /// For compatibility with mbed RawSerial
+    /// @param str null terminated string
+    /// @param block idle wait for every @ref putc to complete
+    /// @return written chars (without terminating null)
+    int puts(const char * str, bool block= true);
+    
+    /// @brief Print a formatted string.
+    /// 
+    /// Idle blocking!
+    /// Dynamically allocates needed buffer.
+    /// @param format null terminated format string
+    /// @return written chars (without terminating null)
+    int printf(const char * format, ...);    
+    
+    
+    ////////////////////////////////////////////////////////////////
+    // Buffer Infos
+    
+    /// @brief Checks if TX buffer is full
+    /// @return true - TX buffer is full, false - else
+    inline bool isTxBufferFull()    {   return txCount == txBufferLength;    }  
+    
+    /// @brief Checks if RX buffer is full
+    /// @return true - RX buffer is full, false - else
+    inline bool isRxBufferFull()    {   return rxCount == rxBufferLength;    } 
+    
+    /// @brief Checks if TX buffer is empty
+    /// @return true - TX buffer is empty, false - else
+    inline bool isTxBufferEmtpy()   {    return txCount == 0;    }    
+    
+    /// @brief Checks if RX buffer is empty
+    /// @return true - RX buffer is empty, false - else
+    inline bool isRxBufferEmpty()   {    return rxCount == 0;    }
+    
+    
+    /// @brief Checks if TX buffer is writeable (= not full).
+    /// 
+    /// For compatibility with mbed Serial.
+    /// @return true - TX buffer is writeable, false - else
+    inline bool writeable()     {   return !isTxBufferFull();   }
+    
+    /// @brief Checks if RX buffer is readable (= not empty).
+    /// 
+    /// For compatibility with mbed Serial.
+    /// @return true - RX buffer is readable, false - else
+    inline bool readable()      {   return !isRxBufferEmpty();   }
+};