Using Queued Serial
Overview of the queued serial links
The QSerial library converts one or both of the serial ports on the PDQ Single Board Computer (SBC) to use interrupt-based queued operation instead of the standard polled operation to process incoming and outgoing characters. A queue is a first-in/first-out buffer that holds outgoing characters waiting to be sent, or incoming characters that have been received by the processor's serial hardware but have not yet been processed by the application program. The output and input queues are managed by interrupt routines, thus decoupling the rate of serial data exchange from the response speed of the C-language application program. Compared to polled serial, which requires that the application program service the serial port often enough to ensure that no characters are missed, these interrupt-based serial drivers can increase the reliable throughput of serial transactions. This chapter describes how to use the pre-compiled queued serial library. It's as easy as including a file from your application source code and then invoking an installation function. All of the standard serial routines (such as putchar, getchar, printf and scanf in C) will then operate using the queued serial drivers.
The Freescale 9S12 (HCS12) processor on the PDQ Board contains two hardware serial ports named Serial1 and Serial2. Each of the two serial channels is implemented by a hardware UART (sometimes called a USART) capable of full duplex RS232 communications, meaning that both transmission and reception can occur simultaneously. In other words, each "local" serial port can both send data to and receive data from a "remote" serial port on the other end of a connecting serial cable. Each channel can also be configured for half duplex communications using the RS485 protocol.
The serial interface is asynchronous, meaning that there is no clock transmitted along with the data. Rather, the transmitter and receiver must be communicating using a known "baud rate", or bit frequency. Both the local and remote serial ports must be configured for the same baud rate. Software-selectable baud rates up to 115,200 baud are supported. Standard attainable baud rates are 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, and 115,200 baud. The default rate for Serial1 and Serial2 is 115,200 baud; you can change it using the operating system's Baud function as defined in the C Function Glossary (A-H).
Software device driver functions
A pre-coded library is provided to implement the queued serial driver in either C or Forth, thus simplifying the design of instrument control applications. Both C and Forth source code versions of a demonstration program are provided to illustrate how to use the library in an RS232 multitasking application.
Simplest method: UseQSerial1 and UseQSerial2 do all the work
The operating system implements serial input/output (I/O) using three "revectorable" serial primitive routines called Emit, Key, and AskKey (called ?KEY in Forth). Each of these serial primitive routines can be revectored by storing the execution address of an appropriate function into a specified location in a task area. Each task defined by the application program has its own vectors that specify the operation of these three primitives. All of the higher level serial I/O routines invoked within a task will use the specified primitive behavior. The principles of serial I/O and a detailed description of tasks are presented in the Serial Communications Chapter.
The use of the queued serial library is simple. To convert the Serial1 port to use queued operation in a specified task, simply execute the Use_QSerial1()
function within the task. This initializes two 128-byte buffers, one for outgoing characters (the transmit queue), and one for incoming characters (the receive queue). It writes the execution addresses of queued versions of Emit, Key, and AskKey into the task user area, ATTACHes an queued serial interrupt service routine to the serial port, and enables interrupts. After executing this function, all high level serial I/O routines invoked from that task will use the queued serial driver.
With this approach, to use two serial ports simultaneously, at least two tasks must be defined in your application, one for each serial port. To convert the Serial2 port to use queued operation, execute the Use_QSerial2()
function in the task that uses the Serial2 port. After executing this function, all high level serial I/O routines invoked from that task will use the queued serial driver. All of the low level details of managing the queues are handled by the library functions, so the application code is the same as it would be for polled operation. The advantage is that fast bursts of up to 128 incoming characters from the remote will be buffered in the input queue until your application program gets around to processing them; this avoids potential data loss.
To revert to the default polled operation in a task using Serial1, invoke the function named Standard_Serial1()
. Similarly, calling Standard_Serial2()
reverts to the default polled operation of Serial2 in the invoking task.
Explicit use of the queued serial primitive functions
In some applications, the programmer may wish to avoid using an additional task to communicate on a second queued serial link, thereby conserving common RAM (each task uses over 1 Kbyte of common RAM area). For example, assume that a single task invokes Use_QSerial1 to enable queued serial communications via the serial1 port. If this same task wants to receive a serial data stream from the serial2 port, the program can invoke the Init_QSerial2 function, globally enable interrupts, and then repeatedly call QKey2 to obtain the incoming queued data from the serial2 port. In this case there is no need to set up a second task to acquire the serial2 data. The task can output data to the serial2 port by explicitly calling QEmit2. Note that the print and scan routines invoked in the task will use serial channel one in this case, because this is the channel that was configured with Use_QSerial1.
The Use_QSerial1 function invokes Init_QSerial1 and then proceeds to revector the serial primitives to call QEmit1, QKey1, and QAskKey1 (Q?Key1 in Forth). In this example, the application does not revector the serial2 routines; they are initialized using Init_QSerial2, and then QEmit2, QKey2, and QAskKey2 (Q?Key2 in Forth) are explicitly invoked by the application code.
Queued RS485 communications
The queued serial drivers can be used for both RS232 (full duplex) and RS485 (half duplex) serial communications. Using RS485 is described here; it involves setting a hardware jumper and using the operating system functions RS485Init(), RS485Transmit(), and RS485Receive() (RS485.INIT, RS485.TRANSMIT, and RS485.RECEIVE in Forth) to manage the direction of the data transfer.
One common error to be avoided is changing the RS485 direction from transmit to receive before all outgoing characters have been sent to the remote. To avoid this problem, the application program should poll the Transmit_Q_Empty()
function after transmitting a line of characters before changing to receive mode. Transmit_Q_Empty()
returns a true flag when all characters in the transmit queue have been sent out, but due to double buffering in the HCS12 processor, the application program should wait until Transmit_Q_Empty()
returns a true flag, and then should delay two character transmission time (twenty times the inverse of the baud rate) to allow the last character to be shifted out before changing to RS485 receive mode.
Queued serial demonstration programs
Both C and Forth demo programs are provided to illustrate how to use the queued serial channels.
The comments in each source code file present an explanation of the program. Simply load the program and run the top level routine to see the queued serial ports in action. To monitor the performance of the second serial port, you can move your serial cable to the Serial2 connector on the PowerDock. If you will be developing an application that uses both serial ports, consider purchasing two USB serial adapters from Mosaic when you order a development kit to enable viewing data on both ports at once.
Installing the queued serial library software
From the MosaicPlus C environment, C programmers can invoke the queued serial library by including the following line in their source code file:
#include "qserial.h"
Forth programmers can invoke the queued serial library by including the qserial.fin
file for the latest kernel version from the folder C:\MosaicPlus\forth\libraries\firmware\
, with a #include
directive for Mosaic Terminal after calling DEFAULT.MAP (or another custom memory map initialization).
Glossary
Disable_QSerial1
C: void Disable_QSerial1( void )
4th: Disable_QSerial1 ( – )
Undoes the effect of Init_QSerial1
and reverts to the default polled (non-queued) operation of Serial1. Locally disables the interrupt enable bits for serial port 1, clears the SERIAL1_RESOURCE variable. Assumes the calling task did not have its higher-level serial I/O routines revectored for queued operation on Serial1, and does not restore the standard higher-level serial I/O routines. Disable_QSerial1
is automatically called by the higher-level function Standard_Serial1
.
Availability: Mosaic IDE+ revision 1500 or greater
Disable_QSerial2
C: void Disable_QSerial2( void )
4th: Disable_QSerial2 ( – )
Undoes the effect of Init_QSerial2
and reverts to the default polled (non-queued) operation of Serial2. Locally disables the interrupt enable bits for serial port 2, clears the SERIAL2_RESOURCE variable. Assumes the calling task did not have its higher-level serial I/O routines revectored for queued operation on Serial2, and does not restore the standard higher-level serial I/O routines. Disable_QSerial2
is automatically called by the higher-level function Standard_Serial2
.
Availability: Mosaic IDE+ revision 1500 or greater
Init_QSerial1
C: void Init_QSerial1( void )
4th: Init_QSerial1 ( – )
Initializes interrupt-driven queued serial I/O on the Serial1 port. Initializes the queues, clears the SERIAL1_RESOURCE variable, ATTACHes and locally enables the serial interrupt service routine. This function does not globally enable interrupts; the calling routine must invoke ENABLE_INTERRUPTS if interrupts are not already enabled before using the queued serial routines. After calling Init_QSerial1 and enabling interrupts, the functions QEmit1
, QKey1
, and QAskKey1
(Q?Key1
in Forth) will work properly. Init_QSerial1
is automatically called by the higher level function Use_QSerial1
.
Availability: Mosaic IDE+ revision 1231 or greater
Init_QSerial2
C: void Init_QSerial2( void )
4th: Init_QSerial2 ( – )
Initializes interrupt-driven queued serial I/O on the Serial2 port. Initializes the queues, clears the SERIAL2_RESOURCE variable, ATTACHes and locally enables the serial interrupt service routine. This function does not globally enable interrupts; the calling routine must invoke ENABLE_INTERRUPTS if interrupts are not already enabled before using the queued serial routines. After calling Init_QSerial2
and enabling interrupts, the functions QEmit2
, QKey2
, and QAskKey2
(Q?Key2
in Forth) will work properly. Init_QSerial2
is automatically called by the higher level function Use_QSerial2
.
Availability: Mosaic IDE+ revision 1231 or greater
QAskKey1
C: int QAskKey1( void )
4th: Q?Key1 ( – int )
A queued version of AskKey1 (?Key1
in Forth). GETs the Serial1 resource variable, checks whether there are any characters in the Serial1 input queue, and RELEASEs the Serial1 resource variable. Returns a TRUE flag (-1) if there is at least one character in the input queue, otherwise returns a FALSE flag (0).
Availability: Mosaic IDE+ revision 1231 or greater
QAskKey2
C: int QAskKey2( void )
4th: Q?Key2 ( – int )
A queued version of AskKey2 (?Key2
in Forth). GETs the Serial2 resource variable, checks whether there are any characters in the Serial2 input queue, and RELEASEs the Serial2 resource variable. Returns a TRUE flag (-1) if there is at least one character in the input queue, otherwise returns a FALSE flag (0).
Availability: Mosaic IDE+ revision 1231 or greater
QEmit1
C: void QEmit1( uwchar c )
4th: QEmit1 ( char – )
A queued version of Emit1. GETs the Serial1 resource variable, writes a character into the transmit queue (if there is no room in the queue, this routine calls Pause to allow other tasks to run while waiting), and RELEASEs the Serial1 resource variable. The interrupt routine posted by Init_QSerial1
or Use_QSerial1
emits the character as soon as the serial transmitter is available.
Availability: Mosaic IDE+ revision 1231 or greater
QEmit2
C: void QEmit2( uwchar c )
4th: QEmit2 ( char – )
A queued version of Emit2. GETs the Serial2 resource variable, writes a character into the transmit queue (if there is no room in the queue, this routine calls Pause to allow other tasks to run while waiting), and RELEASEs the Serial2 resource variable. The interrupt routine posted by Init_QSerial2
or Use_QSerial2
emits the character as soon as the serial transmitter is available.
Availability: Mosaic IDE+ revision 1231 or greater
QKey1
C: int QKey1( void )
4th: QKey1 ( – int )
A queued version of Key1. GETs the Serial1 resource variable, waits for and returns the next character in the Serial1 input queue (if there is no character in the queue, this routine calls Pause to allow other tasks to run while waiting), and RELEASEs the Serial1 resource variable.
Availability: Mosaic IDE+ revision 1231 or greater
QKey2
C: int QKey2( void )
4th: QKey2 ( – int )
A queued version of Key2. GETs the Serial2 resource variable, waits for and returns the next character in the Serial2 input queue (if there is no character in the queue, this routine calls Pause to allow other tasks to run while waiting), and RELEASEs the Serial2 resource variable.
Availability: Mosaic IDE+ revision 1231 or greater
Standard_Serial1
C: void Standard_Serial1( void )
4th: Standard_Serial1 ( – )
Undoes the effect of Use_QSerial1
and reverts to the default polled (non-queued) operation of Serial1 in the calling task. Locally disables the interrupt enable bits for serial port 1, clears the SERIAL1_RESOURCE variable, and revectors the calling task's serial primitives to point to the default Emit1, Key1, and AskKey1 functions.
Standard_Serial2
C: void Standard_Serial2( void )
4th: Standard_Serial2 ( – )
Undoes the effect of Use_QSerial2
and switches to polled (non-queued) operation of Serial2 in the calling task. Locally disables the interrupt enable bits for serial port 2, clears the SERIAL2_RESOURCE variable, and revectors the calling task's serial primitives to point to the Emit2, Key2, and AskKey2 functions.
Transmit_Q_Empty
C: uint Transmit_Q_Empty( uint serial_id )
4th: Transmit_Q_Empty (serial_id – flag )
Returns a TRUE flag if the transmit queue of the specified serial port is empty; otherwise returns a FALSE flag. The input parameter is serial_id = 1
for the Serial1 port, or serial_id = 2
for the Serial2 port.
When using RS485, a common error to be avoided is changing the RS485 direction from transmit to receive before all outgoing characters have been sent to the remote. To avoid this problem, the application program should poll the Transmit_Q_Empty()
function after transmitting a line of characters before changing to receive mode. Transmit_Q_Empty
returns a true flag when all characters in the transmit queue have been sent out, but due to double buffering in the HCS12 processor, the application program should wait until Transmit_Q_Empty
returns a true flag, and then should delay two character transmission time (twenty times the inverse of the baud rate) to allow the last character to be shifted out before changing to RS485 receive mode.
Two character transmission times is approximately 20 times the inverse of the baud rate. For example, at 115,200 baud (the default rate), each character takes just under 90 microseconds to be shifted out. Thus passing the parameter 180 to the MicrosecDelay function would allow enough time for the final two characters to be transmitted after Transmit_Q_Empty indicates that the queue is empty; then the RS485 transmitter can safely be changed to receive mode without disrupting the transmission.
Use_QSerial1
C: void Use_QSerial1( void )
4th: Use_QSerial1 ( – )
Installs interrupt-driven queued serial I/O on the Serial1 port in the calling task. Initializes the queues, clears the SERIAL1_RESOURCE variable, revectors the calling task's serial primitives to point to the queued versions of Emit, Key, and AskKey functions, ATTACHes the serial interrupt service routine, and globally enables interrupts. After invoking this function, serial I/O operations in the specified task will use queued (buffered) drivers.
Use_QSerial2
C: void Use_QSerial2( void )
4th: Use_QSerial2 ( – )
Installs interrupt-driven queued serial I/O on the Serial2 port in the calling task. Initializes the queues, clears the SERIAL2_RESOURCE variable, revectors the calling task's serial primitives to point to the queued versions of Emit, Key, and AskKey functions, ATTACHes the serial interrupt service routine, and globally enables interrupts. After invoking this function, serial I/O operations in the specified task will use queued (buffered) drivers.
See also →