Wednesday, August 23, 2017 12:00 am

Writing Your Own Serial Port Processing Function Using Callbacks

Written by 
Rate this item
(0 votes)
Writing Your Own Serial Port Processing Function Using Callbacks Photo Credit: Lobo Studio Hamburg

Handling serial data streams is our bread and butter. In a majority of cases, NetBurner’s serial device platforms are ready for your Serial-to-Ethernet applications right out-of-the-box; however, in applications that require precision serial data timing, additional processing may be needed. In this article, we’ll show you an implementation of callback functions to handle these scenarios.

The Problem

NetBurner serial device platforms are pre-loaded with a buffered interrupt-driven serial port driver, and for most use cases, that is all you need to handle serial communications – straight out of the box. However, there are situations in which you may want to process received serial data in your application. For example, the end of a MODBUS-RTU (MODBUS Remote Terminal Unit) response message is defined as a silent interval or inter-frame delay of at least 3.5 character times (aka. t3.5 or 3.5 char) between successive bytes in the message stream. In applications that require precision serial data timing, this inter-frame delay can cause issues and can be mitigated with additional processing on the receiving end.

The Solution

One solution for applications that require precision timing on the serial stream is to use a callback function with a decrementing timer that resets to a known timing value each time a character is received. When the timer count reaches zero, it generates an interrupt and you know the message frame receipt is complete. Your application can then process the message. NetBurner modules offer a number of timers that can be used for this purpose. The focus of this article will be on the serial interrupt handling.

For this application example to work, a rapid timer reset is essential. Therefore, the key factor is low-latency received character processing to reset or update the timer. Each time a character is received, a serial interrupt occurs and is processed in the serial interrupt service routine. The timer will be reset for each character, and only timeout if the proper gap occurs. You could modify the NetBurner serial port driver code, but a preferred and perhaps lower risk method would be to keep the application-specific code in your project. This can be accomplished with a callback function.

Implementation

The implementation is slightly different between SBL2e-based products and our larger NetBurner platforms such as the SB800 EX (for more detail visit this product comparison table). But in both cases your callback function will be executed in place of the system’s serial driver receive character processing. This means in addition to controlling the timer, your callback function will also need to process the incoming data, since it will no longer be buffered by the NetBurner system. For example, you may want to buffer the incoming characters.

SBL2e Implementation

Note that the serial port must be opened in interrupt mode (not polled mode).

#include <serialirq.h>

// This function will be called by the ISR for each received serial character
void MyRecievedSerialDataCallback(uint8_t dataByte)
{
// Add code to handle received characters
// Add code to refresh timer
}

// Open the serial port in interrupt mode
InitIRQUart(uartNum, baudrate, 1, 8, eParityNone);
Or
SimpleUart(uartNum, baudrate);


// Assign the serial port receive data handler pointer to your function. 
SerialPortCallBack[uartNum] = MyRecievedSerialDataCallback;

Each time a serial data byte is received it will call your function rather than the system driver. It will happen as soon as the interrupt is serviced, which should be within a few microseconds of when the data byte is received.

There is a complete serial callback example, and Programmable Interval Timer (PIT) example in the ..\nburn\examples\SBL2e folder and is provided after purchase and registration of the device through the support portal. See Appendix A for sample source code.

All Other Platforms

Note that the serial port must be opened in interrupt mode (not polled mode). If you are using a serial port in polled mode, or are uncertain, call the close() function on that serial port and re-open. Anytime an OpenSerial function is called the port will be in interrupt mode.

#include <serialinternal.h>


// This function will be called in the ISR for each received serial character
void MyReceiveSerialDataCallback(int32_t uartNum, uint8_t dataByte)
{
// Add code to handle received characters
// Add code to refresh timer
}

// Open the serial port in the normal way
SimpleOpenSerial(uartNum, baudrate);


// Assign the serial port data handler pointer to your function. 
UartData[uartNum].m_pPutCharFunc = MyReceiveSerialDataCallback;

There is a complete serial callback example located at: ..\nburn\examples\StandardStack\serial\SerialReceiveCallback and is provided after purchase and registration of the device through the support portal.

Timer examples are located at ..\nburn\examples\[platform], where [platform] is your specific NetBurner device. See Appendix B for sample source code.

Appendix A: SBL2e Example Code Listing

/*------------------------------------------------------------------------------
 * Example to show how to intercept the serial port receive processing.
 *----------------------------------------------------------------------------*/
#include <predef.h>
#include <basictypes.h>         // Include for variable types
#include <serialirq.h>          // Use UART interrupts instead of polling
#include <constants.h>          // Include for constants like MAIN_PRIO
#include <system.h>             // Include for system functions
#include <netif.h>
#include <ethernet_diag.h>
#include <autoupdate.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <system.h>


extern "C" {
    void UserMain(void *pd); // prevent C++ name mangling
}

const char * AppName = "SBL2e Serial Callback";

#define RX_BUF_SIZE 256

volatile BYTE indexGet;
volatile BYTE indexPut;
volatile BYTE CircBufferArray[RX_BUF_SIZE];

/*-----------------------------------------------------------------------------
 * This function is called by the serial interrupt service routine. No I/O
 * functions may be called from here. Only RTOS post functions may be called.
 * ---------------------------------------------------------------------------*/
void SerialReceiveCallback(unsigned char dataByte)
{
    CircBufferArray[indexPut++] = dataByte;

    if ( indexGet == indexPut )
        indexGet++;

    if (indexPut >= RX_BUF_SIZE)
        indexPut = 0;

    if (indexGet >= RX_BUF_SIZE)
        indexGet = 0;
}

/*-------------------------------------------------------------------
 * UserMain
 *-----------------------------------------------------------------*/
void UserMain(void *pd)
{
    SimpleUart( 0, SystemBaud );    // initialize UART 0
    SimpleUart( 1, SystemBaud );    // initialize UART 1
    assign_stdio( 0 );              // use UART 0 for stdio

    // Assign UART1 serial receive processing to our callback function
    SerialPortCallBack[1] = SerialReceiveCallback;

    InitializeStack();

    iprintf("Waiting for Ethernet link...");
    {
        WORD ncounts = 0;
        while ( ( !bEtherLink ) && ( ncounts < 2 * TICKS_PER_SECOND ) )
        {
            ncounts++;
            OSTimeDly( 1 );
        }
    }
    iprintf("complete\r\n");

    EnableAutoUpdate();
    OSChangePrio( MAIN_PRIO );  // set standard UserMain task priority

    iprintf( "Application built on %s on %s\r\n", __TIME__, __DATE__ );
    while ( 1 )
    {
        OSTimeDly( 1 );
        while ( indexGet != indexPut )
        {
            iprintf( "%c", CircBufferArray[indexGet++] );

            if ( indexGet >= RX_BUF_SIZE )
                indexGet = 0;

        }
    }
}

Appendix B: Standard Example Code Listing

/*------------------------------------------------------------------------------
 * Example to show how to intercept the serial port receive processing.
 *----------------------------------------------------------------------------*/
#include <predef.h>
#include <basictypes.h>
#include <buffers.h>
#include <serinternal.h>
#include <constants.h>
#include <system.h>
#include <init.h>
#include <ucos.h>
#include <autoupdate.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <serial.h>


extern "C" {
    void UserMain(void *pd);
}

const char * AppName = "Serial Receive Callback";

#define RX_BUF_SIZE 256

volatile BYTE indexGet;
volatile BYTE indexPut;
volatile BYTE CircBufferArray[RX_BUF_SIZE];

/*-----------------------------------------------------------------------------
 * This function is called by the serial interrupt service routine. No I/O
 * functions may be called from here. Only RTOS post functions may be called.
 * ---------------------------------------------------------------------------*/
void SerialReceiveCallback(int uartNum, unsigned char dataByte)
{
    CircBufferArray[indexPut++] = dataByte;

    if ( indexGet == indexPut )
        indexGet++;

    if (indexPut >= RX_BUF_SIZE)
        indexPut = 0;

    if (indexGet >= RX_BUF_SIZE)
        indexGet = 0;

}

/*-------------------------------------------------------------------
 * UserMain
 *-----------------------------------------------------------------*/
void UserMain(void *pd)
{
    init();			// Initialize network 
    EnableAutoUpdate();
    OSChangePrio( MAIN_PRIO );

    // Open serial port
    SimpleOpenSerial(1, 115200);

    // Assign UART1 serial receive processing to our callback function
    UartData[1].m_pPutCharFunc = SerialReceiveCallback;

    iprintf( "Application built on %s on %s\r\n", __TIME__, __DATE__ );
    while ( 1 )
    {
        OSTimeDly( 1 );
        while ( indexGet != indexPut )
        {
            iprintf( "%c", CircBufferArray[indexGet++] );

            if ( indexGet >= RX_BUF_SIZE )
                indexGet = 0;
        }
    }
}

References and Further Reading:

Read 76 times Last modified on Thursday, August 24, 2017 3:13 am

Leave a comment

NetBurner Learn

The NetBurner Learn website is a place to learn faster ways to design, code, and build your NetBurner based product. Sign-up for our monthly newsletter!

Latest Articles