## Sign up for our Newsletter here ##
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).
Code: Select all
#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;
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.
Code: Select all
#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;
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
Code: Select all
/*------------------------------------------------------------------------------
* 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;
}
}
}
Code: Select all
/*------------------------------------------------------------------------------
* 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;
}
}
}
MODBUS over Serial Line Specification and Implementation Guide V1.02:
http://www.modbus.org/docs/Modbus_over_ ... _V1_02.pdf
Asynchronous serial communication:
https://en.wikipedia.org/wiki/Asynchron ... munication