Many databases applications, like those developed under Visual dBASE, are designed for a multi-user environment, where several clients share a common database. This type of distributed architecture depends upon the services provided by a network to interconnect several machines so that they can share and exchange information
To cope with particular requirements, it is sometimes necessary to connect a specific equipment to a local client machine such as a modem, and to exchange data with only this equipment. Different solutions are available for such connections, the best known types are parallel and serial connections. In this article, we are going to consider serial communications, available on all PCs, and compliant with the RS232-C standard.
This type of interface has been around since the beginning of the computer era, and, in many technical domains, remains a good and efficient way to perform point to point connections between two computers or equipment. Even now, in the Internet and worldwide network age, many users still connect to the world wide web using a modem that is linked to their computer with a “serial” cable using an RS232 interface.
Accordingly, computer applications — including Visual dBASE's — are sometimes required to operate this RS 232 serial link to exchange data with equipment or another computer hooked to the machine on which it is running.
This article, while introducing a new release of a Visual dBASE component named SerialComm 1.21 (attached to this document) presents the fundamentals of serial communications, their specific operational requirements on PC computers, their use from the Win32 operating system, and the integration of the SerialComm custom class component in a Visual dBASE application.
The article is divided into the following chapters:
As a general introductory topic, a short retrospective of the origins of serial communications presents some of the more interesting facts that drove the transmission techniques from their discovery to modern technologies.Introduction Hardware basics Full duplex versus Half duplex communications Flow control, handshake Asynchronous communications Transmission parameters and errors Architecture of a Communication application under Windows Serial Communication under Visual dBASE 7 using the SerialComm 1.21 package Using the SerialComm 1.21 component An application of the SerialComm 1.21 component: a mini TTY console Conclusion
This chapter will give a simple overview of the hardware basics needed to understand the way a serial communication port works, from a software point of view. A full description of the hardware methods and details to connect and operate the serial port is given in a separate document, hardware description, but it is not necessary to read it to understand the present article. It is included for the interested reader, so that all the topics from hardware to software are covered.
DTE and DCE Devices
Two terms are commonly used in serial communications, DTE and DCE. DTE stands for Data Terminal Equipment, and DCE stands for Data Communications Equipment. Your computer is a DTE device, while most other devices are usually DCE devices. A DTE is connected to a DCE with a straight cable, while two like devices must be connected with a cross wire one. See hardware description for more information about serial cables.
You can simply replace the term
“DTE device” with “your PC” and the term “DCE device” with “remote device”
in the following discussion. The following table gives the signals involved
in the serial communication port:
Abbreviation | Full Name | Function |
|
|
DTE
Serial Data Output (TD)
Data is sent from a DTE device to a DCE device on this wire. |
|
|
DTE
Serial Data Input (RD)
A DTE device receives data on the RD (receive data) wire. |
|
|
The DTE device puts this line in a mark condition to tell the remote device that it is ready and able to receive data. If the DTE device is not able to receive data (typically because its receive buffer is almost full), it will use this line as a signal to the DCE to stop sending data. When the DTE device is ready to receive more data (i.e., after data has been removed from its receive buffer), it will place this line back in its original state. |
|
|
The complement of the RTS wire is CTS. The DCE device uses this line to tell the DTE device that it is ready to receive the data. Likewise, if the DCE device is unable to receive data, it will change this line's state. |
|
|
DSR (Data Set Ready) is the companion to DTR in the same way that CTS is to RTS. |
|
|
|
|
|
A modem uses Carrier Detect to signal that it has made a connection with another modem, or has detected a carrier tone. |
|
|
Its intended function is very similar to the RTS line. Some serial devices use DTR and DSR as signals to simply confirm that a device is connected and is turned on. The DTR and DSR lines were originally designed to provide an alternate method of hardware handshaking. |
|
|
A modem toggles the state of this line when an incoming call rings your phone. |
Note: The Carrier Detect
(CD) and the Ring Indicator (RI) lines are available only in connections
to a modem. Because most modems transmit status information to a PC when
either a carrier signal is detected (i.e., when a connection is made to
another modem) or when the line is ringing, these two lines are rarely
used.
Full
duplex versus half duplex communications
Achieving a half duplex or a full duplex communication process can depend upon the transmission media, or the way the application handles the incoming stream of data.
Performing full duplex communication is generally better because it is more efficient in terms of effective data flow, and does not require any particular protocol to arbitrate the channel. However, it requires the capability of simultaneously sending data and receiving incoming data be present at both ends.In half duplex communication mode, characters are sent only one way at a time. For instance, CW radio communications, or coaxial ethernet (10B2), are half duplex media that can support only one direction; otherwise, a collision would occur and the desired messages would be lost. An application can also be designed as half duplex if it cannot process simultaneous transmit and receive data streams.
In full duplex communications mode, data can be exchanged in both ways simultaneously. In RS232 communications, full duplex transmission is possible, as one wire is dedicated to transmission and another to reception.
Flow control is needed when the application that sends data transmits the data at a higher rate than the application that receives this data can process it. For instance, if our DTE to DCE speed is several times faster than our DCE to DCE speed, sooner or later data is going to get lost in the DCE as buffers overflow. Flow control has two basic varieties: hardware or software.
Hardware flow control, also known as RTS/CTS flow control, uses two wires in the serial cable. When the DTE computer wishes to send data, it activates the Request to Send line. If the DCE has room for this data, then the modem will reply by activating the Clear to Send line, and the computer starts sending data. If the DCE does not have the room, then it will not assert Clear to Send.
Even though these two types of flow control can achieve the same results, it is generally better to use hardware flow control because it is more efficient. On the other hand, using both flow types of control is generally useless.Software flow control, sometimes expressed as Xon/Xoff, uses two extra characters (Xon and Xoff) transmitted in your data lines. Xon is normally indicated by the ASCII 17 character; the ASCII 19 character is used for Xoff. The DCE will have only a small buffer, so when the DTE computer fills it up, the DCE sends a Xoff character to tell the DTE computer to stop sending data. Once the DCE has room for more data, it then sends a Xon character and the computer sends more data. This type of flow control has the advantage not requiring any more wires since the characters are sent via the TD/RD lines. However, on slow links, each character requires about 10 bits, which can slow communications down.
Let's consider the synchronization problem: the receiver is going to track the state of its receive wire line, and will see a succession of state changes, “0”s and “1”s, from which it will have to recognize the characters.
There are two possible ways to decode the incoming bitstream:
The first one consists of sending a given pattern, called a Synchronization Character, that the receiver will recognize and deduce the start of a character. The Synchronization Character is always sent when the transmitter is not sending user data, in order for the receiver to “Synchronize” on the transmitter. This communication scheme is called “Synchronous communications”.
The second one, with which we are dealing in this article, is called “Asynchronous”, and means “no synchronization”. It therefore does not require sending and receiving idle characters. However, the beginning and end of each byte of data must be identified by start and stop bits. The start bit indicates that the data byte is about to begin, and the stop bit(s) signals when it ends. The requirement to send these additional bits cause asynchronous communication rates to be slightly slower than synchronous rates. However, it has the advantage that the processor does not have to deal with the additional idle characters.
An asynchronous line that is idle is identified with a value of 1, also called a mark state. By using this value to indicate that no data is currently being sent, the devices are able to distinguish between an idle state and a disconnected line. When a character is about to be transmitted, a start bit is sent. A start bit has a value of 0, also called a space state. Thus, when the line switches from a value of 1 to a value of 0, the receiver is alerted that a data character is about to come down the line.Transmission parameters and errors
The Start bit
After receiving the start bit, the receiver can synchronize an internal clock, which depends on the line speed. The clock keeps tabs on the timing and samples the line at evenly spaced instants to determine the value of the transmitted bits.
The Data bits
There may either be 5, 6, 7, or 8 data bits, depending on the physical line configuration parameters. Both the receiver and the transmitter must agree on the number of data bits, as well as the baud rate. Almost all devices transmit data using either 7 or 8 data bits.
Notice that when only 7 data bits are employed, you cannot send ASCII values greater than 127. Likewise, using 5 bits limits the highest possible value to 31.
Baud Versus Bits Per Second
The baud unit has its origins in Jean Maurice Emile Baudot, who was an officer in the French Telegraph Service. He is credited with devising the first uniform-length 5-bit code for characters of the alphabet in the late 19th century. What baud really refers to is modulation rate or the number of times per second that a line changes state. This is not always the same as bits per second (BPS). If you connect two serial devices together using direct cables then baud and BPS are in fact the same. Thus, if you are running at 19200 BPS, the line is also changing states 19200 times per second. But when considering modems, this isn't the case.
Because modems transfer signals over a telephone line, the baud rate is actually limited to a maximum of 2400 baud. This is a physical restriction of the lines provided by the phone company. The increased data throughput achieved with 9600 or higher baud modems is accomplished by using sophisticated phase modulation and data compression techniques.
The speed defines explicitly the size of each individual bit. In theory, any speed is possible, but only the following ones are available on a PC:
(*) The speeds of 128000 and 256000 baud are not available on all hardware, and depend upon the type of circuit used.
Available speeds, in bits per second (or baud)
110 4800 56000 300 9600 57600 600 14400 115200 1200 19200 128000 (*) 2400 38400 256000 (*)
The Stop bit(s)
After the data has been transmitted, a stop bit is sent. A stop bit has a value of 1, a mark state, and it can be detected correctly even if the previous data bit also had a value of 1. It is recognized because of the stop bit's duration. Stop bits can be 1, 1.5, or 2 bit periods in length.
There can be different combinations of number of data bits and number of stop bits, presented in the following table:
The data sent using this method is said to be “framed”, i.e., the data is framed between a Start and Stop Bit(s). Should the Stop Bit(s) be received as a Logic 0, then a framing error will occur. This is a common occurrence, when each side is communicating at a different speed.
Number of Data bits Number of stop bits 8 1 or 2 7 1 or 2 6 1 or 2 5 1 or 1.5
The parity bit
Besides the synchronization provided by the use of start and stop bits, an additional bit called a parity bit may optionally be transmitted along with the data, just between the data bits and the stop bit(s). A parity bit affords a small amount of error checking, to help detect data corruption that might occur during transmission. You can choose either even parity, odd parity, mark parity, space parity or none at all. When even or odd parity is being used, the number of marks (logical 1 bits) in each data byte are counted, and a single bit is transmitted following the data bits to indicate whether the number of 1 bits just sent is even or odd.
For example, when even parity is chosen, the parity bit is transmitted with a value of 0 if the number of preceding marks is an even number. For the binary value of 0110 0011 the parity bit would be 0. If even parity were in effect and the binary number 1101 0110 were sent, then the parity bit would be 1. Odd parity is just the opposite, and the parity bit is 0 when the number of mark bits in the preceding word is an odd number. Parity error checking is very rudimentary. While it will tell you if there is a single bit error in the character, it doesn't show which bit was received in error. Also, if an even number of bits are in error, then the parity bit would not reflect any error at all.
Mark parity means that the parity bit is always set to the mark signal condition and likewise space parity always sends the parity bit in the space signal condition. Since these two parity options serve no useful purpose whatsoever, they are almost never used. SerialComm proposes however the “Mark” parity as a possible choice.
In summary, the parity bit follows the following rules:
Each time a character is received by the receiving hardware, if the parity check is enabled, the hardware will recompute the corresponding parity and compare it with the parity bit received. If they are identical, the hardware declares it correct. If they are different, the hardware declares a parity error.
Parity type Description None No parity bit added Odd if number of 1 in the received character is odd, then parity bit is 0. Even If number of 1 in the received character is even, then parity bit is 0 Mark Logic 1 (Idle state)
The Break Signal
Last, we can introduce the notion of “Break” Signal. This is when the data line is held in a Logic 0 state for a time long enough to send an entire character. Therefore if you don't put the line back into an idle state, then the receiving hardware will interpret this as a “break” signal.
A sample waveform
The diagram below shows the expected waveform of a character when using 8 Data bits, No Parity and 1 Stop Bit. The RS-232 line, when idle, is in the Mark State (Logic 1). A transmission starts with a start bit which is Logic 0. Then each bit is sent down the line, one at a time. The LSB (Least Significant Bit) is sent first. A Stop Bit (Logic 1) is then appended to the signal to make up the transmission.
The diagram shows the next bit after the Stop Bit to be Logic 0. This must mean another word is following, and this is its Start Bit. If there is no more data coming, then the receive line will stay in its idle state (Logic 1).
The above
waveform applies to the Transmit and Receive lines on the RS-232 port that
carry serial data. The other lines on the RS-232 port which, in essence
are parallel lines (RTS, CTS, DSR, DTR, CD and RI) are also at RS-232 Logic
Levels.
Architecture
of a Communication application under Windows
Generally speaking, a communication application will be in charge of:
Even if the two processes - sending and receiving - look rather symmetric and similar, from an algorithmic point of view, there are major differences bewteen them:sending the desired characters on the line receiving incoming characters managing the handshake signals and protocol.
This can be viewed also as sending and receiving, because managing input lines or incoming Xon/Xoff characters can be considered as a receiving process, while managing output lines or sending Xon/Xoff characters can be viewed as a sending operation.
|
|
|
|
Consists of polling the communication port and status lines to check whether new data is available or if a control signal has changed. | Consists of calling the function that reads the communication device - and thus the new incoming character or signal state change - only when the communication device hardware signals it by generating a hardware interrupt. The function called when an interrupt occurs is named an “Interrupt Service Routine”, or ISR. |
|
|
|
|
|
|
During the good old times of
DOS applications, use of an interrupt-driven application was available
only to assembly or C programmers, because they could directly access all
the hardware resources of the machine. The drawbacks of such applications
were that there was a high level of software complexity, they were difficult to
debug, and they provided low maintenance abilities.
In a few words, an interrupt is a way to inform and signal a microprocessor which is executing a software program in memory, that external hardware events occur. An interrupt line corresponds to a hardware input, supervised by the microprocessor. When it becomes active, the microprocessor immediately stops its current execution to process another program (called the interrupt handler or the interrupt service routine) before returning to the code it was previously executing before the interruption arrived.
The arrival of Windows has swept away all these low level techniques, as this operating system controls the management of all the hardware resources of the machine. Applications must no longer directly address the hardware resources. An application addresses Windows, which, in turn, operates any hardware resource, especially the communication ports.
At first glance, this may look like a regression, because an interrupt handler cannot be anymore settled as before. But in fact, Windows kernel sets up already, to communicate with the serial ports, an interrupt process and fills up, when characters are received, an input buffer that the application can read when necessary. Let's look in greater detail how the communication application is going to be designed now under Windows:
The receiving data stream The receiving stream can be seen as shown on the diagram provided further on. The main feature of this diagram consists in a receive (Rx) queue, that behaves as a FIFO buffer (First In, First Out):
- The receive queue is accessed in write mode by a Windows process, which is hardware interrupt-driven, at the frequency rate of the incoming characters. This frequency can be relatively high. For instance, at 9600 baud, the character rate can go up to 1000 Hz, or one character each 1 millisecond.
Note that the port status, both hardware control lines and Xon/Xoff protocol, is also entirely managed by Windows. The application merely needs to configure which handshake to use, and to check for protocol events.
- To get the incoming information from the COM port, two methods, similar to polling and interrupt, can be considered for this receive queue:
|
|
|
|
Ask Windows to get the next characters from the receive queue, extracting them from the FIFO buffer, and the port status. This is different from direct polling on the COM port, as discussed before, because here several characters can be read at a time. If characters are read 10 by 10, the read function is called at a tenth the maximum character frequency, and executes 1/10 times the same application code. | Windows is configured so that it signals the application the events that occur on the communication port. In Windows 16 bits, this was achieved using WM message. In Windows 32 bits, it uses thread synchronization and events. Both act like an interrupt process, with the difference being that the ISR will execute here only when the operating system activates. |
|
|
|
|
|
|
A simplified view of the communications scheme under Windows can be illustrated with the following diagram.The sending data stream The sending stream is less complex, and is shown on the same following diagram. It also contains a transmit (Tx) queue, but here, this queue is not compulsory if we accept the transmit part of the communication application to be synchronous. That is to say, it will wait until the desired action on the communication port to be fulfilled before continuing further processing.
As for receiving, Windows can be configured so that the transmit process is asynchronous so that it can signal the application, with analog techniques, that the requested action has been fulfilled.
The Windows kernel sets up its
Application Programming Interface (API) with which the application (in
our case, the Visual dBASE program) dialogs. The Windows kernel also sets
up the Receive (Rx) queue and the Transmit (Tx) queue, which can be accessed
both by the Windows API functions, and by Windows Interrupt handlers. Those
handlers work asynchronously with the application. They can dialog synchronously,
through the hardware drivers, with the hardware, which in turn drives the
physical line. The hardware can also signal events (that can be: new character
received, control line state change, ...) to the interrupt handlers and
to a control API, which in turn translates them into events to the application.
Serial
Communication under Visual dBASE 7 using the SerialComm 1.21 package
Attached to this article is the SerialComm 1.21 software package that you can easily retrieve with the link at the bottom of this page. Here is the list of the Visual dBASE files it contains, along with a short description of each of them:
It is recommended, but not required, that you include the SerialComm component in your component palette, at least during the time you're getting used to it and to the demo forms.
The four demo forms included in the package and listed above are of various levels of complexity. I recommend that you run and examine these demo programs in the following order:
Now that the main substance concerning serial communications has been explored, let's go back to our business applications and their need to communicate on serial communications.
Obviously, the choice between the polling-like and the interrupt-like methods will be determined by your needs:
If you are in the first situation, then you should forget about Visual dBASE, go to something like C or C++ and use interrupt-like methods.whether you need an application with fast real-time abilities and immediate reaction to external events and single character reception,
you require reasonable performance, with the major concern being the ability to exchange data with satisfactory timings.
On the other hand, if your application deals mostly with data exchange through the RS-232C media, with rather weak real- time requirements, then the SerialComm component, using a polling-like method should be your choice.
This SerialComm component is contained in a custom class named SerialComm.cc. It contains the required properties and methods to operate a serial communications handler using the Windows API. It's written totally in Visual dBASE 7 code, and provides an easy way to design your communication application under Visual dBASE.
The following description is a general guideline on how you should use this component and design your application, taking into account the general interface parameters. For details on a given parameter, please refer to the component's documentation itself, contained in the source code's comment header.
Let's now go through the steps to design the communication application:
In Windows 98 (32 bits), a communication port is seen exactly like a standard file. You first open it, write and read data, and finally close it.
For instance, opening the COM2 port at 9600 baud with no parity, 8 data bits, and 1 stop bit will be performed by:- The communication port name: COM1, COM2, COM3 or COM4 to which your serial cable is hooked
- the baud rate, from 300 to 115200 (or more if hardware supports it)
- the data bits per character, 5, 6, 7, or 8
- the number of stop bits, 1, 1.5 or 2
- the parity, if any, can be “None” (no parity), “Odd”, “Even”, or “Mark”
oSerialCommRef.openPort( "COM2", 9600, "None", 8, 1 )where oSerialCommRef is the SerialComm component's reference.
The communication port is set by the openport() method to a default “no handshake” configuration. In this mode, the DTR and RTS signals will be activated, but these control lines, as well as the DSR and CTS signals, will not be used to control the incoming or outgoing data flow.
To set a particular protocol handshake
method, two functions are provided in the component: SetXonXoffConfig()
and SetHandshake().
Likewise, the Xon limit, as a percentage of the receive queue, defines a lower limit. If an Xoff was previously sent, and the number of characters in the receive queue gets below the Xon limit, then an Xon character is sent.
When the port is opened, a default configuration for the Xon/Xoff characters and queue limits is set up. These default settings are the following:
- Xon ASCII code is set to 0x11 (= 17)
- Xoff ASCII code is set to 0x13 (= 19)
- Xon limit is set to 20%
- Xoff limit is set to 80%
- The Rx queue size is the default one, set up by Windows.
- Then, for both hardware and Xon/Xoff protocol, you will call the SetHandshake() method, passing the desired handshake type as a parameter. The available handshake types are the following:
Note that no possibility to mix the hardware protocol with the software protocol exists. As has been discussed earlier, it is useless to do so. However, if you require a specific protocol other than the ones available in this list, then it is possible to add your own. Use the corresponding section of the component's source code, in the SetHandshake() function:
- No handshake (set as default when the port is opened): 0
- Xon/Xoff: 1
- RTS/CTS and DSR/DTR: 2
- DTR/DSR only: 3
- RTS/CTS only: 4
// If needed, one can add here additional handshaking methods; the dcb
// changes can be set the same way as for the 5 methods proposed above.case ( HSmethod == 6 ) // My protocol
// My protocol's statements
// ...
otherwise ...Reading data from the communication port Now that we have successfully opened the COM port and set the protocol, we are ready to receive and transmit data on the serial line.
As we have seen previously, the thing to remember about the receiving end is that we don't know when we're going to receive characters on the line. There are two ways to get the incoming characters: by directly reading the communication port, or by using a polling-like method:
- Directly reading the communication port:
The simplest method is to issue repetitive read commands to the port, reading new characters if available. While always possible using the readstring() function of the component, this polling method is not recommended because it may occupy the CPU at full load while looping through the COM port.
- A polling-like method:
There is a more concise method to get the read process to behave more like the interrupt-driven scheme.The idea here is to read several characters at a time from the input buffer in order to poll it less often. This allows less frequent execution of the part of code that processes those characters.
If we take into account the time Tc taken by a character to be received on the line, then we can compute the time taken by a group of characters to be received, assuming each character arrives immediately after the preceding one.
Suppose, in these conditions, we want to read a maximum of n characters at a time. Then we can ask for new characters at an interval equal to n times a character length. We will call this interval Tnc, which obviously is:
(1) Tnc = n.Tc
The character time Tc will depend upon the line parameters, defined when the port is opened. As we have seen before, a character will include, in addition to its data bits, one start bit, stop bit(s), and, if requested, a parity bit:
(2) Tc (s) = (1 + databits + stopbits + iif( parity check, 1, 0)) / baudrate
This is a first step. In order to be fully asynchronous and have our read task be called only if new characters have arrived, we need to set up an independent mechanism that will read the input buffer. This will be accomplished by a Visual dBASE timer, whose interval time will be set to Tnc. The timer is going to poll the communication port at this interval, and, if new characters are available, call a user function that will process those characters.
Here, we have to be careful with some timing and sizing issues:
- Visual dBASE timers can run at a minimum interval time of 55 ms. Even if you set an interval time less than 55 ms, the timer will run at 55 ms. Thus, the parameter n should be chosen so that the Tnc interval time is greater than 55 ms, otherwise, and because reading will be performed during 55 ms and not during the expected lower interval time, more than n characters could be read during the Tnc interval.
(3) Tnc > 55 ms
- The size of the receive buffer should be larger than n, as it must be able to contain n characters between two successive reads from the timer. In practice, because there can be some jitters on the timer ticks due to Windows or processing at that time, it is better to keep some margin and set a minimum size of 2.n. This is generally not a problem, as the default receive queue size set up by Windows will be generally of 4096 chars.
(4) 2.n < rxBufSize
- Last, it is important that the processing time (Tp) the user routine will take to process each time the n maximum characters are received be less than the timer interval Tnc. Otherwise, the timer tick will be postponed until the user routine returns.
(5) Tp < Tnc
The second requirement necessary to cope with asynchronism is the capacity to call the function that processes the read characters only when needed, as would an interrupt routine.
As we don't know when new characters arrive, the only way is to regularly read the input buffer for new characters, using a timer set to the Tnc interval. Because we read and process characters at a much lower rate (Tnc) than the character one (Tc), this will not be as time critical as if we were reading character by character. If new characters have arrived, then a function, whose reference (Visual dBASE Function Pointer) has been previously supplied, will be called.
This asynchronous process of regularly reading the receive queue is called here a “continuous read”.
In order to initiate a continuous read on the communication port, the startContinuousRead(n,fpUserISR)function will be used. This function will take 2 parameters as input:
- n: the maximum number of characters to read each time. In fact, the effective number of chars read each time can vary. It can, of course, be less than n if less characters have arrived, but it can sometimes be more than n if there are some jitters on the timer ticks, or if VdB is occupied when the tick arrives.
- fpUserISR: the Visual dBASE function pointer of the user ISR function that will be called each time new characters arrive on the port. When calling this function, a parameter will be passed, that will be a Visual dBASE string containing the new characters received since the last call.
For example, the user ISR can be declared like this in a form:
function processRxData( cNewChars )
// process the characters in cNewChars
...
returnLet's say we want to process, for instance, characters at a 100 ms rate (and larger than 55 ms), assuming that the port is opened at 9600 baud, 8 bits of data, no parity, and 1 stop bit. To initiate the continuous read after the COM port is opened, we will need to set the maximum number of characters read at each call to:
n = 0.1 / ((1 + 8 + 1 + 0) / 9600) = 96 characters
The following line of code can be placed just after the COM port is opened.
// Read 96 max characters each time
oSerialCommRef.startContinuousRead( 96, this.processRxData )At this point, the function processRxData() of the form will be called only if new characters have arrived on the line.
Note that the number of characters, 96, must be lower than the default Rx queue size, in general 4096, to meet requirement (4).
To stop a continuous read, the stopContinuousRead() method can be invoked without parameters. It will stop the timer and any further read from the COM port.
Writing to the communication port As opposed to the read process, the write process (and this is only a design choice) will be synchronous. We could alternatively have set a Tx queue in order to make the transmit process asynchronous, so that the characters to transmit would be placed in this Tx queue and wait there to be transmitted. This was not our design choice for two reasons:
Since we'll be using synchronous transmission, the Tx queue's length is set (by default also in Windows) to 0.
- The fact that the application stops and waits until the characters to send have been effectively transmitted is not undesirable,
- If an error occurs during transmission, then the application can know it immediately as the return from the call will indicate an error.
Now, writing a string of characters to the COM port from Visual dBASE can be done using one of two methods of the SerialComm component:
- The first method, writeString(), will send the string provided as an input parameter in one block. From an application point of view, the consequence is that no read can be performed during this operation, even if a continuous read is previously started, because Visual dBASE's timer ticks cannot interrupt a Windows API function. So while this function can be used to send characters, it should not be used in situations where characters are read using the startContinuousRead() process.
- The second method, fullDuplexWrite(), has been designed especially to deal with the situation where a read using startContinuousRead() must be performed while transmitting characters. So for full duplex mode, this method will:
- stop the continuous read
- split the string to send, provided as parameter, into substrings of length n, the same n as for startContinuousRead(),
- alternatively:
- Send each substring.
- If the continuous read was enabled at the time fullDuplexWrite() was called, read the COM port. If characters are available, then call the user ISR to process them.
- After the last substring is sent, place the continuous read in the same state it was before the call to fullDuplexWrite().
By adopting these steps, the requirement to read and write data at the same time can be met. As fullDuplexWrite() can be used in any situation, it is thus preferable to use it rather than writeString().
Dealing with communication events Communication events will be classified into two types:
These two types of events can be trapped with the SerialComm component.
- Reception line errors can originate from hardware. For instance, electromagnetic signals, and, more generally, interferences can generate noise and jam the serial line signals. This situation can be observed, for instance, if the cables are long, are running at high speed, and lie in the vicinity of electrical systems.
- Protocol events will influence the transmission of data in certain situations. Such is the case if the host receiver is not ready to accept new data.
- Reception line errors can be checked using the checkCommErrors() method of SerialComm.
- In case no errors have occurred, this method does not update any error information, and simply returns true, meaning no error occurred.
- If an error occurred, the method will update the .CommErrors property of the component, which contains two arrays that will be filled with the types of errors that occurred. It will also reset these error flags.
As a general rule, it is recommended to check, after each reception of characters, the reception line errors by issuing a call to the checkCommErrors() method. This also has the effect of resetting the reception error flags.
The errors that can be reported are the following:
Error text string set in .CommErrors.text Error type set in .CommErrors.type Meaning Break 1 A break signal has been detected on the line Framing 2 An incorrect character framing was detected Char overrun 3 A char overrun occurred due to incorrect stop bits Parity 4 The parity check was unsuccessful Transmit overflow 5 This case cannot actually happen, as no transmit buffer is used. Receive overflow 6 The receive buffer is full, and new characters will be lost. This can occur if no protocol is set, or if no read orders are issued and characters arrive on the line.
- Protocol events and port status
The protocol events will influence the behavior of the transmit task of both the receive and the transmit process. According to the protocol set using the SetHandshake() method, different behaviors are possible when a write to the COM port is requested:
- No protocol set
In this case, it is always possible to transmit characters on the line, whatever the state of the status lines. There will be no errors reported from any write action.
- Xon/Xoff handshake
For this protocol, writing to the COM port will not be allowed if either:
- An Xoff character is received, meaning that we are requested to stop further transmission. Further transmission will be enabled as soon as the Xon character is received.
- An Xoff character is sent, meaning that the input queue has filled up and has triggered the Xoff character. Although stipulated in the API OLH that the choice is given to enable or disable transmissions when the Xoff is sent, Windows 95 seems to ignore such a condition and always disables further transmissions until the Xon is sent. That is, when the input queue has been emptied down to the limit that triggers the Xon transmission.
Before writing to the COM port, It is always possible to know whether transmission will be allowed, by calling the checkPortStatus() method. This method will update the .PortStatus property of the component, which contains itself two useful properties for dealing with Xon/Xoff protocol:
If we attempt to write to the COM port when the transmission is disabled, the writeString() or fullDuplexWrite()methods will return false, meaning that transmission was not allowed, or a stop situation occurred during the transmission of the string passed as parameter. In that case, it is possible to know how many characters have not been sent, by checking the .PortStatus.CharsNotSent property of the component. When transmission is enabled again, the characters not sent must be written again to the port.
- The .PortStatus.XoffHold tells, if true, that we are in the first above case, an Xoff has been received, and we are expecting now an Xon to be received in order to enable transmissions again.
- The .PortStatus.XoffSent indicates, if true, that we are in the second situation, an Xoff has been sent, and now we are waiting for the input queue to be emptied enough (by reading it) so that the Xon can be sent and the flag reset.
- Hardware protocols
A Hardware protocol is set when choosing either the RTS/CTS, the DSR/DTR , or both of them. With these protocols, writing to the communication port will be disabled if:
As for the Xon/Xoff protocol, before writing to the COM port, it is always possible to know whether transmission will be allowed, by calling the checkPortStatus()method. This method will update the .PortStatus property of the component, which contains two useful properties dealing with hardware protocol:
- The CTS signal is not asserted, and the RTS/CTS protocol is selected.
- The DSR signal is not asserted, and the DSR/DTR protocol is selected.
- The .PortStatus.CTSHold gives the status of the CTS signal. If true, the signal is not asserted.
- The .PortStatus.DSRHold gives the status of the DSR signal. If true, the signal is not asserted.
As for the Xon/Xoff protocol, attempting to write to the COM port when the transmission is disabled, using the writeString() or the fullDuplexWrite() methods, will lead these methods to return false.This means that transmission was not allowed, or a stop situation occurred during the transmission of the string passed as a parameter. In that case, it is possible to know how many characters have not been sent, by checking the .PortStatus.CharsNotSent property of the component. When transmission is enabled again, the characters not sent must be written again to the port.
An application of the SerialComm 1.21 component: a mini TTY consoleIt is always possible to ask for the port status in order to get the state of the above flags by using the checkPortStatus() method. Another benefit of calling that method is that it will reset the communication errors, if any.Closing the communication port A communication port is handled by Windows like any other file on your system. When you open a file, you need to close it or you won't be able to access it, unless you close your application. That's why a communication port needs also to be closed after it has been used. This is easily achieved by simply calling the closePort() method of the component:
oSerialCommRef.closePort()
where oSerialCommRef is the SerialComm component's reference.
Miscellaneous functions Some miscellaneous features have also been added to perform some additional tasks:
- The .displayMessages property
This property, set to true by default, enables the component to display its internal error messages. Turning this property to false will disable these messages, but you can always know the result of any method by testing its return code.
- The sendbreak() method
This method will send a break signal on the transmission line for a default duration of 1 second. You can supply your own duration, as an input parameter of the sendbreak() method.
- The flushPort() method
This method will flush the receive and transmit queues, deleting all the characters they contain.
After having overviewed all the SerialComm component's possibilities, let's now turn to an application that uses this component. We'll go over the main implementation techniques needed to get the best efficiency and results from it.
The application proposed here is a mini TTY console, which is a kind of terminal emulator that will be enabled to receive and send data in full duplex mode, and display this data in an editor window. This may remind you in some way the “hyperterminal” application that ships with Windows. Its name is TTYdemo.wfm, and it is delivered as part of the SerialComm 1.21 package. (See the link at the end of this article).
This form has two pages. The first one, shown on startup, presents a screen editor which is used both to display the incoming data from the serial port and to enter data to be sent to the same port. The following image presents an example of a session which was obtained while connecting to an ISP provider through a modem. The commands entered on the keyboard are:
In addition to the screen editor, the top of the window contains a set of comboBoxes to choose the communication port and physical line parameters, and a red light that turns to green if the communication port is successfully opened. The bottom of the window has some buttons for specific actions:
Clicking on the Errors and Status button will display the following screen:The Send BREAK button will send a .5 s break signal on the line. The Echo OFF toggle button enables or disables the local echo of keystrokes to the editor, The Clear Window button erases the editor. The Errors and Status button will display the second page of the form. The second page summarizes all the port status and reception error counts for the current session.
On the second page, the Received Errors box contains a set of received error counters. The Receive Status box indicates if an Xoff has been sent as well as the count of unread characters in the receive queue. A Transmit Errors and Status box tells if the transmission is possible or blocked, and, in that case, the number of unsent characters. A set of buttons will also perform:
All these components are set up with standard Visual dBASE techniques, and we will not get into more detailed explanations here. On the other hand, we will take an in-depth look at how the different steps described in the Using the SerialComm 1.21 component chapter are used here in this typical communication application.the TTY Window button will display the form's first page, the Update Status button manually updates the current status of the port, the Reset error counts button resets all the error counters to zero.
685 // Linked method: form's onOpen eventOpening the communication port and initiating the read process. This first part is accomplished in the form's onOpen event with the following piece of code:
Line 690 uses the .version property of the component to display its version.764 // Linked method : Combobox Handshake CB_HDSK onChange eventLine 693 disables the internal error messages of the component since this app will use the component's return codes to determine if an error occurred.
Line 696 opens the communication port, given the physical line parameters and port number selected using the comboBoxes. As we will see further on, each time a comboBox is changed, this onOpen method will be executed again.
Line 704/705 computes the number n of characters to read from the receive buffer in the continuous read process so that the read period is 100 ms.
Line 708 will begin the continuous read process, each time getting an expected maximum of n characters and using the ISR callback function this.newCharsReceived()
Using the openPort() and startContinuousRead() return codes, line 709 will turn the light to green (if true) or red (if false).
Setting the protocol handshake The protocol comboBox will call the following code when it is changed:
According to the protocol comboBox value, lines 767 to 780 will determine the corresponding handshake code (0 to 4) and set an Xon/Xoff configuration.752 // Linked method: ComboBox for COM port selectionLine 781 will try to set this handshake type. The light will reflect the execution status of the protocol change.
Line 783 will force the update of the Error Status screen. In the case of a hardware handshake selected, this can report immediately a transmit hold condition, if, for instance, the expected hardware signals (CTS or DSR) are not present.
Changing the port's parameters The five comboBoxes (CB_PORT, CB_SPEED, CB_PARITY, CB_STOP, and CB_DATA) dedicated to choosing the port parameters, will call the onChange event routine of the similarly-named ComboBox:
First, the port is closed by calling the form's canClose event. Then it is reopened by calling the form's onOpen event. When reopening the communication port, the new line parameters selected will be taken into account. Line 756 also sets up the selected handshake method.
812 // User-defined Interrupt Service RoutineReading data from the communication port When the port is successfully opened, the onOpen event (as seen above) initiates a continuous read process, calling regularly the newCharsReceived() method. Let's have a look on what this newCharsReceived() method is doing:
First, this method receives a parameter, here cReceivedString. Each time this method is called, it will receive the new characters in this parameter string.786 // Linked method: Editor's key eventThen, lines 818 to 822 will parse the received string to see if control characters have been received. Control characters have an ASCII code below 32, and cannot be displayed directly. We will display them using the following code:
chr(1) will be displayed ^A
Lines 824 and 825 then display the processed string in the editor window and scrolls to the bottom.
chr(2) will be displayed ^B
...
chr(31) will be displayed ^_The next line, 826, will check if reception errors occurred. Reception errors should be checked each time characters are received. The description of the updateErrors() method follows.
Writing to the communication port This application is designed so that each time a new character is entered on the keyboard, it will be sent immediately to the communication port. For instance, if the application was designed to send a file, it would be better to send strings with several characters at a time, so that it is faster, i.e., less time is lost between each character. At the most, a complete file stored in a string can be sent as a whole, the full duplex mode being entirely managed by the component itself. The characters to send are processed here by the editor's key event.
We first check lines 791 to 800 to see if the local echo is enabled by the Echo ON/OFF pushbutton, and if a control character has been entered on the keyboard. If so, it is displayed using the same rules as the incoming characters that are received from the communication port. A control character can be entered on the keyboard using the combination Ctrl + <Letter> :719 // This method updates the status fields of the status and errors panel.Ctrl-A will enter a chr(1)Then, the character is sent on the communication port (line 803) using the fullDuplexWrite() method. This method performs the transmission of data, while still allowing the reception of incoming data. Note that because we send only one character at a time, we could also have used the writeString() method. Sending a single character is performed in much less time than the receiving timer tick, which is set here to a duration of 100 ms.
Ctrl-B will enter a chr(2)
...On line 806, if an error occurred during transmission (fullDuplexWrite() returned false), the count of unsent characters is updated by one, and the updateStatus() method is called to update the Error and Status screen.
This function returns 0 (line 810) because the character must not be displayed. This is done directly inside the function (as seen above).
Dealing with communication events and getting the port status We have just seen that the read and write functions used in this application were calling two particular methods, named updateErrors() and updateStatus(), in order to check for communication errors (after each read) and the port status (after each write). Let's focus now on those two functions.
- The updateStatus() function, called when a write to the communication port returned false.
This function is detailed hereunder. The main action appears in line 721, where, as stated before, the call to the checkPortStatus() method of the SerialComm component will first reset the reception errors, if no new errors are present. Then it will set the .PortStatus property of the component according to the communication port status.Lines 722 to 726 simply update the display with the new status.
729 // This method updates the receive error counts and status of the status and errors panel.
- The updateErrors() function, called after each read, checks for read errors.
This function is listed below. Here also, the key point (line 732) consists of a call to the checkCommErrors() method of the SerialComm component.
- If no error occurred (checkCommErrors() returns true), nothing is performed, so very little time is taken for this operation.
- In the opposite case (checkCommErrors() returns false), an update (lines 733 to 747) of the error counters is performed on the Errors and Status page. For that, the .CommErrors.type[] array is scanned, and errors types encountered updated.
Then, the reception errors are cleared (line 748) with a call to updateStatus(). Clearing the reception error flags is important. Otherwise, you would get a previous error type reported each time a new error occurs.
712 // Linked method: form's canClose eventClosing the communication port This action will be triggered if:
By closing the communication port in the form's canClose event, the port will be closed when the form is closed, or when requested explicitly by a direct call to this method.
- The TTYdemo form is closed by clicking in the close [x] button, or,
- If the port's parameters are changed in such a way that it is necessary to close it and reopen it to take the new parameters into account.
110 this.PB_BREAK = new PUSHBUTTON(this)Miscellaneous functions The TTYdemo application is also able to send communication Break signals. This is accomplished by the Send BREAK pushbutton. The onClick event of this pushbutton is simply linked to a codeblock that will perform this action. The form's constructor code contains the following lines:
Line 112 will send a break signal of 0.5 seconds when the button is clicked.Conclusion
RS232, as a serial communication standard available on all PCs, still remains a good alternative for exchanging point to point information between computers or equipment.
Designing a software application that handles the serial communication ports has never been an easy task, even during the DOS age, as the algorithms necessary to set up have to cope with external hardware events. Moreover, timing aspects involved in such applications often requires notions of real time software programming.
Windows has now slightly changed this landscape, as it manages and controls all the hardware resources of the machine. In fact, it has masked some principles using intermediate software layers, but the basics still remain the same.
However, its kernel has opened a way to design low level like communication applications with high level development tools such as Visual dBASE. Using its high level programming language, focus can be turned to the design and conception aspects of the project. These are of prime importance in reaching a satisfactorily and efficient result.
To finish this article, I would like to say a few words about the SerialComm project. It started 1-1/2 years ago, as I needed to design an application that could connect and download data from a server through a modem and a telephone line. The first version of this component (1.01) was issued in September 1998.
At this time, I noticed that many people on the Borland newsgroup were seeking a similar tool, so I decided to share this already-designed code.
It already included many functions. But some basic areas, like status and errors report, handshaking, were absent or rather weak. As I needed to improve this part of the component for better robustness against non nominal cases in that application, I achieved a second version of it, SerialComm 1.2, and then 1.21 while writing this article.
Additionally, after the first release of the component, I received some feedback and questions, sometimes dealing with elementary notions on operating the serial communications. I hope this article, which can be used as a general guideline on the subject, will be able to come up to those expectations.