Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

DScope: Snooping on the Comm Line


Dr. Dobb's Journal March 1997: C Programming

Al is a DDJ contributing editor. He can be contacted at [email protected].


Several years ago, my brother Fred was working on a project for a client. He was building what he called a "data scope," which, he explained, is a device to insert between two other devices that communicate with each other with RS-232 serial ports. The data scope captures the serial input bytes from one device, records the byte values on a video screen, and passes them through to the other device. The capture works in both directions so that you can view an asynchronous, bidirectional data flow. A data scope is handy in the lab to debug the serial communications between prototype devices. You can also use one to reverse engineer the protocols of devices with closed architectures or, if you have a nefarious bent, to snoop on serial communications between unsuspecting parties.

Fred's project hit a wall. His data scope was what we now call an "embedded system." It used the state-of-the-art 2-MHz Intel 8080 8-bit microprocessor. He had designed the serial circuits and video buffers for maximum efficiency and he had written his firmware in assembly language with tight code. However, the system could not keep up with current fast baud rates. Some systems were actually blazing along at 1200 baud. Fred's data scope kept overrunning its capture buffers.

Memory fades, but I think Fred eventually solved his bandwidth problem by tightening up the code, adding memory for bigger buffers, changing to the newer 4-MHz Z80, or some combination of those things. Imagine what he could have done with a P-133 with 40 MB of RAM and an advanced software-development environment such as Windows 95 and Visual C++. Just imagine. Read on.

Sweet Thang: Raising Cane

Besides our mutual interest in computers, Fred and I shared another legacy, one that must have been passed down from some unidentified ancestor. Diabetes. Fred was what is called a "brittle diabetic," which means that he could not keep his blood glucose levels under reasonable control, and the side effects ultimately took his life. I am more fortunate, being not particularly brittle and having acquired the condition later in life at a time when better control tools are available.

I do not tell you this story to gain your sympathy. Instead, my condition is the groundwork for this month's "C Programming" project, and it would not make much sense if you did not know its purpose. The project, a data scope, is not only for use by diabetics, but you will learn how it helped this one. If the software is useful to you, you can express your appreciation by sending a few careware bucks to the American Diabetes Association, 2 Park Avenue, New York, NY 10016.

There are advantages to this medical inconvenience. I don't have to give blood. I would, out of a sense of duty and public spirit, but they won't have me. Mosquitoes avoid me. I have no problems with vampires, either. And, whenever I look a little pale, all the ladies in my life (wife, daughters, mother-in-law, several cocktail waitresses) fuss over me and try to feed me. I've learned to live with all that.

It is now known that diabetic patients who monitor their blood glucose levels at home and adjust their diet, exercise, and medication accordingly have a significantly better chance of avoiding the side effects later on. Until just a few years ago, we had to depend on occasional visits to the doctor and expensive lab work to check our blood glucose levels. These checks were necessarily infrequent and not necessarily revealing of the true nature of the condition. I, for one, always adhere to the rules more carefully one or two days before the appointment than I do the rest of the time. Everyone wants to please the doctor and avoid a sound scolding.

Several years ago, diabetics got a better chance when home blood glucose testing devices became available. You prick your finger, squeeze a drop of blood on a test strip, stick the strip in the machine, and get the bad news 45 seconds later rendered in milligrams of glucose per deciliter of blood. Uh, oh. Shouldn't have eaten that prune Danish. You keep a log of the readings and take the log to the doctor -- when you remember to write it down, that is, and when you don't lose the scrap of paper that you wrote it down on.

One Touch II

My home testing machine is called the "One Touch II." I chose this particular one from the many that are available because it has an RS-232 port and because the company sends you a cable and software for the asking, which eliminates the lost-scrap-of-paper test log. You get attached to these cute little machines. Not only are they neat embedded systems, but they constantly remind you to take better care of yourself.

The free program from the One Touch people looks lame by today's standards. It is a DOS text-mode program that requires the old PRINT.EXE memory-resident driver. It programs the One Touch II parameters and displays, prints, and downloads to a text file the last 300 test results.

Doctors, bless 'em, are mostly knee-jerk practitioners. They prognosticate and prescribe based mainly on what they see right now. The economics of an office practice and the fifteen minutes of their time that you get (at least 45 minutes after the appointment time) do not permit an in-depth correlation of your data and analysis of your condition. If you want them to work from a collection of data about your particular situation, you have to collect, record, and provide the data yourself. Give them all the information in one gulp and make it easy to digest because, whatever else happens, fifteen minutes later they're with the next patient, and you are at the front desk paying the bill.

The One Touch printout presents only part of the information necessary to controlling diabetes, and its all-text report format demands careful analysis to permit anything more than cursory interpretation. This problem begs for a graph that shows not only your blood glucose levels across the past month or two, but also when you exercised, what you ate, how much insulin you took, and when you shot up. A program to collect and present the data is what I need.

Many of us could write such a program. Any decent spreadsheet program can import the numerical data and prepare the graph. The application could be one of those slick new programs that seems to acquire the Excel user interface through advanced OLE techniques. Data entry for the other elements might necessarily be manual, but the code to gather those elements would be easy enough to write. But why should I have to key in the blood glucose test readings when they reside conveniently in the memory of that One Touch just waiting for a program to download them into a smarter system? What I need to know is the protocol. How does the PC program speak to the test machine? How does the test machine firmware respond? The One Touch II is an embedded system with a closed architecture. The only information that the software tells you is whether it found the test machine, and, if so, on which serial port and at what baud rate.

The One Touch Snub

I called the One Touch company to ask for the protocol. They referred me to their technical MFWIC ("main fellow who's in charge") who wasn't in his office just then. I left a message on his voice mail explaining what I needed. I emphasized the advantage of a modern PC application, one that would be in the public domain and that just might sell more One Touch machines and test strips. To add credibility to my intentions, I alluded to my international fame as a programming luminary and prominent member of the third estate. That ought to make him sit up and take notice. A week later I left a second message reminding him of the first. That was about two years ago. I'm still waiting for him to return my calls. Rodney and me, we don't get no respect.

Grounded

The biggest disadvantage of my diabetes has been, until now, one imposed by Uncle Sam. An FAA medical examination revealed the condition in the first place, and the FAA promptly revoked the privileges of my airman's third class medical certificate. That rule, a so-called "blanket ban" against insulin-treated diabetics, which has been in effect for 37 years, was revoked in December 1996 for those who can maintain a strict medical regimen. Complying means maintaining excellent control of blood glucose levels. Wanting to fly again, I renewed my interest in computer-assisted monitoring and treatment, and decided not to wait any longer for an answer from the One Touch people.

DScope for Windows

What I needed was an upgrade to Fred's data scope to reverse-engineer the communications protocol between the PC and the One Touch II, so I set out to build one. It would be cool, I thought, to run it under Windows 95 with slick menus, dialog boxes, and colors. I've never written a Windows program that uses the communications API, so I spent an afternoon looking at every advanced Windows programming book in my library. Guess what? Nobody has bothered to write that chapter. Not Petzold, not Prosise, not Grinzo, not Cilwa, not Edson, not Jewell, not Holzner. I was stuck with the dense and vague online Help that comes with Visual C++. Between that information and one example program on the VC++ CD-ROM, I was able to get the semblance of a data scope program up and running. I called it "DScope."

Grouchy editorial opinion follows. Microsoft dedicates all its C++ programming talent to building bigger and better versions of MFC. That, I believe, is a business decision. It seems that outside of the MFC and VC++ groups, no one in Microsoft recognizes the advantages of object-oriented programming and C++. It is said that most of Windows 95 was written in C. The MFC group is not without blame, either, being interested mainly in frameworks. They ignore other parts of the Win32 API that cry out for encapsulation. The communications API is one striking example. The MAPI and multimedia APIs are others. Wait until you see the Games SDK. The IShell interface is a nightmare. Given the complexity of the Win32 APIs and the breadth of their coverage, you'd think that Microsoft would try to make things easier by exploiting the power of C++ class hierarchies, particularly since they obviously have people whose jobs are to make the C APIs as obfuscated and cryptic as possible. End of grouchy editorial opinion.

Back to DScope. I would have named the program DataScope, but there's certain to be someone out there with a registered trademark who'd make me change the name. If, by coincidence, DScope is your trademark, forgive me. It's the name of a program not a product. I won't mention it again, which is easy to promise since this project takes only this one column to describe.

My Windows 95 DScope program almost worked. It took a while to get a test machine configured. It had to have two working serial ports and a mouse. Usually, one serial port on my machine hosts the mouse. I tried just disconnecting the mouse only to find that VC++ Developer Studio is less than cordial to mouseless programmers. I had to do some transplants to get a working configuration set up, mainly involving an old bus mouse that I found in the junk pile. IRQs kept getting in the way. The old bus mouse doesn't have that many IRQ options, and my machine has other boards that use them. It took some board finagling, but I finally got it set up. I was delighted to see that DScope for Windows worked. To simulate the PC/One Touch environment, I connected the serial ports of two laptops to the two serial ports on the DScope PC with null modem cables and ran the old DOS version of Procomm in both laptops. I could hammer on the keys of both laptops as hard and fast as I wanted, and it seemed that nothing was lost. The Windows 95 DScope program dutifully gathered, reported, and passed on the serial data from both machines. I couldn't be sure that it was working, however, without a more rigorous and controlled test. I decided to cut straight to the chase.

I connected the One Touch II to one of the serial ports and ran the One Touch program -- named, oddly, Utility -- in the PC that was connected to the other. As long as Utility did not try to query the One Touch II, the little machine's broadcasts came in loud and clear, and my DScope program displayed the bytes in its client window. As soon as I instructed Utility to poll for the One Touch, it sent about 11 bytes and locked up the DScope PC completely. There was no getting out of that program other than with a cold boot. Windows 95 was dead. The mouse cursor wouldn't budge. The keyboard wouldn't respond.

I used the VC++ debugger to trace at the assembly-language level to the point of the crash. A jump into one of the kernel functions never returns.

I tried using Procomm to send the same bytes that Utility had sent, but the lockup would not reproduce itself in that configuration. It must have something to do with how the Win32 communications API works in a two-port environment when the bytes are coming asynchronously at each other at processor speed. Maybe no one has ever tried that before, or maybe I just did something wrong. That's possible given the opaque documentation. Whatever the problem, I concluded that Windows 95 and the Win32 API are probably not fast enough to support a real-time data scope, even on a P-133. I will not publish that program because it does not work. I think that one would need to write such a program at the device-driver level.

DOS for DScope II

Now, like the end of a disaster genre book by Arthur Hailey (Airport, Hotel, and that ilk), all this boring background converges into an exciting climax. Hang onto your seats.

I decided to rewrite DScope to run in DOS text mode. Many programmers know how to write communications programs that run under MS-DOS. Over the years, I've published several in this column. The most recent one was a DOS D-Flat mail program that sent and received e-mail by exercising the DOS command line of a remote Internet site. I called it "IMail" and was immediately enjoined to get off of someone else's trademark.

The Eye-Mail program (attention all lawyers, there's a new name to go after) included a CommPort class that encapsulated the operations of the PC's serial port. With a bit of modification, that class could support the DOS DScope program.

Blasphemy follows. You often hear it preached that C++ classes are inherently reusable -- that, when a class is properly designed, you seldom need to modify it, you simply derive a new class. I used to believe and preach that homily until I realized that it rarely comes true. I use inheritance to subclass things in an application that have a lot in common. A button, dialog box, application window, and menu are all generic windows, so a base window class is appropriate. Circles, rectangles, and triangles are all shapes in a graphics system, and they share the properties of location and orientation in the coordinate system, so a base shape class is called for. I seldom, however, take a problem-domain class from one application and subclass it for another unrelated application. Why do that? The applications do not depend on one another, and I have all the source code to the original class, and the new program has its own requirements, and it is easier to modify code than it is to subclass a class, and the result is cleaner and clearer, and besides, nobody pays me for programming purity. (You can breathe now.) Therefore, I modified rather than subclassed the CommPort class. End of blasphemy.

Figure 1(a) shows the system to be reverse engineered. The One Touch II is connected directly to the PC that runs the Utility program. They communicate through the custom One Touch serial cable that connects them. It's custom because it has a stereo miniplug on one end rather than the usual 9- or 25-pin RS-232 connector. Figure 1(b) shows the same configuration with a DScope PC inserted in the RS-232 line. The One Touch II and the Utility PC are blissfully unaware that their communications are being intercepted. They work together in this configuration exactly as they do when they are connected directly.

My immediate requirements for DScope are simpler than what I implemented; mainly I need to reverse engineer the One Touch II. But, inasmuch as I'm publishing the program, it ought to have more general application. Besides, I am certain to use it for something else.

DScope is a 16-bit DOS text-mode program compiled with Borland C++ 4.5. The program assumes that its PC has COM1 and COM2 available for the two connections. It accepts seven comma-separated command-line arguments. The first four arguments, which are similar to those of the DOS MODE COMn command, specify the baud rate, parity, data bits, and stop bits. The fifth argument specifies the capture buffer size, which needs two bytes per input byte from both ports. The program allows a minimum of 256 bytes for the buffer. The sixth argument is h or n to indicate that the program should use hardware handshaking or none. I'll explain handshaking soon. The seventh argument, which is needed only if the sixth one is h, specifies the maximum time in seconds that DScope allows before timing out and deciding that the receiver is not going to assert a hardware handshake to enable input.

DScope reports the data exchange in color on a DOS text-mode screen. It uses the ANSI.SYS protocols to control the colors. Each port has its own color for its data bytes. There is one color for displayable ASCII and another for nonprinting ASCII, which is displayed in the <00> format. By using a different color, I can tell the difference between nonprinting ASCII bytes and ASCII bytes in the data stream that look like the <00> format. One possible enhancement would be an option that displays all bytes as hex values for when you are looking at binary rather than mostly text exchanges. Maybe later.

Listings One and Two are serial.h and serial.cpp, which implement the CommPort class and its associated CommParameters structure. The CommParameters structure members are the initializing parameters for the serial port. The CommPort constructor accepts a port number and a reference to a CommParameters object. The class assumes that two objects of the class are declared and that they exchange serial data with a common set of serial port parameters as if there was no intervening DScope PC. The public interface is simple: There are a constructor and destructor; there is a function that allows one CommPort object to register its address with the other so that the program can pass the input data of each port to the output port of the other object; and there is a function that returns a pointer to the next intercepted character and its port from the data stream.

CommPort is a reference-counting class. When the first of its two objects is instantiated, the constructor allocates and initializes a capture buffer and hooks the timer-interrupt vector to process timeouts. For each object, the constructor hooks a communications-interrupt vector. Most of the constructor's work is performed in an Initialize member function that writes the control values to the UART's registers to set up the communication. The destructor undoes all that, unhooking the communications-interrupt vector and using the reference counter to know when to delete the capture buffer and unhook the timer-interrupt vector, which is when the last (second) object is destroyed.

CommPort's interrupt service routine does most of the work. There are two static ISR functions, hooked by the constructor to the two communications interrupt vectors. They both call the nonstatic CommInterrupt member function to process the interrupt. CommInterrupt reads the serial input byte that generated the interrupt and sends the byte to the output port of the other machine. It puts the byte and the port number into the capture buffer, which is a circular queue.

To use the CommPort class, a program instantiates two CommPort objects. They take over the communication between the two devices automatically. The program may then passively observe that communication by taking bytes out of CommPort's capture buffer.

Handshaking

If the user-specified hardware handshaking on the command line, CommInterrupt tests the destination port to see if it has turned off its DSR (data set ready) signal. That would mean that the receiver is not ready to receive the byte and is telling the sender not to send any more. In this case, CommInterrupt turns off the sender's DTR (data terminal ready) signal, which should tell the sender not to send any more bytes. Then CommInterrupt launches a timeout waiting for the receiver to assert DSR, after which CommInterrupt transmits the sender's byte to the receiver.

There is a two-second timeout in case the receiver's UART has not emptied its transmitter's character-holding buffer. Two seconds ought to be enough. Any longer than that and something has gone amiss with one of the two components or with the connection itself.

You might wonder why this class does not include the XON/XOFF software handshake as an option. XON/XOFF is an agreement between the two systems being tested. When one of them sends XOFF, it is telling the other not to send anything until the first one sends XON. DScope does not need to get into this exchange because DScope passes all bytes along when they are received, including XON and XOFF.

The Scope

Dscope.cpp (available electronically; see "Availability," page 3) is the program that instantiates the two CommPort objects and observes the data exchange. The most complicated thing it does is parse its command line to ensure that there are no errors. Then it instantiates the two CommPort objects, clears the screen, and goes into a loop displaying on the screen characters that the CommPort ISRs post into the capture buffer. The loop also polls the keyboard to see if you press the Esc key, which terminates the program.

DScope reports timeouts and when the capture buffer overruns before being displayed on the monitor. If the connection seems Okay, try a bigger buffer or longer timeout.

The DScope log scrolls off the top of the screen. If you need to see a bigger display, redirect DScope's standard output to a disk file, run the test, exit from DScope, and view the file with a text editor.

Success

When I used DScope to reverse engineer the interface between the One Touch II and its Utility program running in a PC, I observed the One Touch constantly transmitting words and carriage return characters. The words are the ones that it displays on its 1×6 character LCD screen telling me over and over to insert a test strip. The Utility program uses some part of that data stream to decide that there is, in fact, a One Touch connected. Then a lot of dialog transpires between the two about what the date is, what language mode One Touch is using, and so on. I stepped through the Utility program menus until I got to the part where it requests a dump of the test result history. When I chose the dump command, the Utility program transmitted the three characters, DMP. In response, One Touch sent a line of ASCII text for every test result. The fields are surrounded by quotes and comma separated exactly like the fields in files generated by old Basic programs. Is that it? DMP?

I looked at all the cryptic back-and-forth between the two machines that preceded the DMP message and wondered if all that other stuff was necessary. I exited from Utility, restarted Procomm, and typed DMP so that Procomm would transmit those bytes to the One Touch. Whereupon the little blood-sucker dutifully spilled its guts. And that's all there was to that.

DScope is a general-purpose data-scope program. I've tested it running on a 486/100 at baud rates up to 19200. It served its original purpose well. Thanks to DScope, I can now write a Windows 95 program to collect test results from the One Touch II. But DScope's useful life does not end there. In the past, I've written communications programs and struggled through their debugging sessions because I could not see what the programs were reading and writing on the serial port. Never again.

DDJ

Listing One

// ------ serial.h#ifndef SERIAL_H
#define SERIAL_H


</p>
const int maxports = 2;
// ------------ timer macros
#define timed_out(timer)         (timer==0)
#define set_timer(timer, secs)   timer=(secs)*182/10+1
#define disable_timer(timer)     timer = -1
#define timer_running(timer)     (timer > 0)
#define countdown(timer)         --timer
#define timer_disabled(timer)    (timer == -1)
#define TIMER  8
// ----------- won't need this next compiler version
typedef int bool;
const bool false = 0;
const bool true  = !false;
// ------- 8259 Programmable Interrupt Controllers
const int PIC01 = 0x21;
const int PIC00 = 0x20;
const int EOI   = 0x20; // End of Interrupt command
// --------------- line status register values
const int TBE = 0x20;
// ------------ modem control register values
const int DTR  = 1;
const int RTS  = 2;
const int OUT2 = 8;
// ------------ modem status register values
const int RLSD = 0x80;
const int DSR  = 0x20;
const int CTS  = 0x10;
// ----------- interrupt enable register signals
const int DataReady = 1;
// ----- serial port initialization parameter byte
union portinit   {
    struct {
        unsigned wordlen  : 2;
        unsigned stopbits : 1;
        unsigned parity   : 3;
        unsigned brk      : 1;
        unsigned divlatch : 1;
    } initbits;
    char initchar;
};
// ---- parameters for serial I/O
struct CommParameters   {
    int  parity;         // 0, 1, 2 = none, odd, even
    int  stopbits;       // 1 or 2
    int  databits;       // 7 or 8
    int  baud;           // 300, 600, etc.
    bool handshake;      // false = none, true = DSR/DTR
    int  buffersize;     // size of input buffer
    int  timeout;        // DTR timeout
};
// ---- the CommPort class
class CommPort {
    int portno;
    portinit initcom;
    static char* mp_recvbuff;
    static char* mp_nextin;
    static char* mp_nextout;
    static int buffersize;
    static int buffercount;
    static int refcount;
    CommParameters& commparms;
    CommPort* OutPort;
    static void interrupt newtimer(...);
    static void interrupt (*oldtimer)(...);
    static int serialtimer;


</p>
    int BasePort()
        { return (0x3f8-((portno)<<8)); }
    int TxData()
        { return BasePort(); }
    int RxData()
        { return BasePort(); }
    int DivLSB()
        { return BasePort(); }
    int DivMSB()
        { return BasePort()+1; }
    int IntEnable()
        { return BasePort()+1; }
    int IntIdent()
        { return BasePort()+2; }
    int LineCtl()
        { return BasePort()+3; }
    int ModemCtl()
        { return BasePort()+4; }
    int LineStatus()
        { return BasePort()+5; }
    int ModemStatus()
        { return BasePort()+6; }
    int irq()
        { return 4-(portno); }
    int vector()
        { return 12-(portno); }
    int ComIRQ()
        { return ~(1 << irq()); }
    void CommInterrupt();
    void Initialize();
    static CommPort* mp_CommPort[maxports];
    static void interrupt (*oldcomint[maxports])(...);
    static void interrupt newcomint1(...);
    static void interrupt newcomint2(...);
    static void interrupt (*newcomint[maxports])(...);
public:
    CommPort(int port, CommParameters& cp);
    ~CommPort();
    void Register(CommPort* pcom)
        { OutPort = pcom; }
    static char* nextchar();
};
#endif

Back to Article

Listing Two

// --------- serial.cpp#include <dos.h>
#include "serial.h"


</p>
void interrupt (*CommPort::oldtimer)(...);
int CommPort::serialtimer = -1;


</p>
// ---- interrupt vector chain and for restoring vector on exit
void interrupt (*CommPort::oldcomint[maxports])(...);
// ---- pointers to CommPort objects
CommPort *CommPort::mp_CommPort[maxports];
// ---- input capture buffer
char* CommPort::mp_recvbuff;
char* CommPort::mp_nextin;
char* CommPort::mp_nextout;
int CommPort::buffersize;
int CommPort::buffercount;
int CommPort::refcount;
// ------ ISRs to count timer ticks
void interrupt CommPort::newtimer(...)
{
    (*oldtimer)();
    if (timer_running(serialtimer))
        countdown(serialtimer);
}
// ---- serial input interrupt service routines
void interrupt CommPort::newcomint1(...)
{
    mp_CommPort[0]->CommInterrupt();
}
void interrupt CommPort::newcomint2(...)
{
    mp_CommPort[1]->CommInterrupt();
}
// --- table of ISR addresses
void interrupt (*CommPort::newcomint[maxports])(...) = {
    CommPort::newcomint1,
    CommPort::newcomint2
};
// --- the object-based serial ISR
void CommPort::CommInterrupt()
{
    int c;
    bool timedout = false;
    // ---- send end of interrupt to the 8259
    outp(PIC00,EOI);
    if (mp_recvbuff != 0)   {
        if (mp_nextin == mp_recvbuff+buffersize)
            mp_nextin = mp_recvbuff;    // circular buffer
        c = inp(RxData());              // read the input
        // --- pass the input to the other port
        if (OutPort != 0)   {
            if (commparms.handshake)   {
                // --- test for hardware handshake
                int ms = OutPort->ModemStatus();
                if ((inp(ms) & DSR) == 0)   {
                    // --- other port has turned off DSR
                    int mc = inp(ModemCtl());
                    // --- turn off DTR for this port
                    outp(ModemCtl(), mc & ~DTR);
                    // --- wait
                    set_timer(serialtimer, 1); //commparms.timeout);
                    enable();
                    while ((inp(ms) & DSR) == 0)    {
                        if (timed_out(serialtimer))  {
                            timedout = true;      // timeout
                            break;
                        }
                    }
                    disable();
                    // --- turn DTR back on for this port
                    outp(ModemCtl(), mc | DTR);
                }
            }
            if (!timedout)  {
                set_timer(serialtimer, 2);
                int ls = OutPort->LineStatus();
                // ---- wait for transmitter buffer empty, other port
                enable();
                while ((inp(ls) & TBE) == 0)    {
                    if (timed_out(serialtimer)) {
                        timedout = true;
                        break;
                    }
                }
                disable();
                if (!timedout)  {
                    // --- pass the received byte to the other port
                    outp(OutPort->TxData(), (char)c);
                    disable_timer(serialtimer);
                }
            }
        }
        if (buffercount == buffersize/2)    {
            // ---- post the buffer overrun so dscope can report it
            *mp_nextin = (portno+1) | 0x80;
            *(mp_nextin+1) = 1;
        }
        else if (timedout)   {
            // --- post the timeout so dscope can report it
            *mp_nextin++ = (portno+1) | 0x80;
            *mp_nextin++ = 2;
            buffercount++;
        }
        else    {
            *mp_nextin++ = portno+1;   // put port in buffer for display
            *mp_nextin++ = (char) c;   // put char in buffer for display
            buffercount++;
        }
    }
}
// ---- construct a CommPort object
CommPort::CommPort(int port, CommParameters& cp) : commparms(cp)
{
    portno = port-1;
    buffersize = commparms.buffersize;
    // ensure that the buffer size is an even number
    buffersize++;
    buffersize &= 0xfffe;
    OutPort = 0;
    oldcomint[portno] = 0;
    if (portno >= 0 && portno < maxports)  {
        // --- set the address of this object for the ISR to use
        mp_CommPort[portno] = this;
        if (refcount == 0)
            mp_recvbuff = new char[buffersize];
    }
    // --- initialize the input buffer
    if (refcount == 0)  {
        mp_nextin = mp_nextout = mp_recvbuff;
        buffercount = 0;
    }
    // --- reference counter
    refcount++;
    Initialize();
}
// ---- destroy a CommPort object
CommPort::~CommPort()
{
    if (OutPort != 0)
        OutPort->Register(0); // de-register with the other port
    // --- is this the last of the CommPort objects to be destroyed?
    if (--refcount == 0)    {
        // --- yes, delete the capture buffer and initialize its pointers
        delete mp_recvbuff;
        mp_recvbuff = 0;
        mp_nextin = mp_nextout = 0;
        // --- unhook the timer interrupt
        if (oldtimer != 0)   {
            setvect(TIMER, oldtimer);
            oldtimer = 0;
        }
    }
    // --- unhook the serial interrupt
    if (oldcomint[portno] != 0)  {
        setvect(vector(), oldcomint[portno]);
        oldcomint[portno] = 0;
    }
}
// -------- initialize the com port
void CommPort::Initialize()
{
    // --- build the line control byte
    initcom.initbits.parity   = commparms.parity == 2 ? 3 : commparms.parity;
    initcom.initbits.stopbits = commparms.stopbits-1;
    initcom.initbits.wordlen  = commparms.databits-5;
    initcom.initbits.brk      = 0;
    initcom.initbits.divlatch = 1;
    outp(LineCtl(), initcom.initchar);
    // --- program the baud rate
    outp(DivLSB(), (char) ((115200L/commparms.baud) & 255));
    outp(DivMSB(), (char) ((115200L/commparms.baud) >> 8));
    initcom.initbits.divlatch = 0;
    outp(LineCtl(), initcom.initchar);
    // ---- intercept the timer interrupt vector
    if (oldtimer == 0)    {
        oldtimer = getvect(TIMER);
        setvect(TIMER, newtimer);
    }
    // ------ hook serial interrupt vector
    if (oldcomint[portno] == 0) {
        oldcomint[portno] = getvect(vector());
        setvect(vector(), newcomint[portno]);
    }
    // --- program the modem control register
    outp(ModemCtl(), (inp(ModemCtl()) | DTR | RTS | OUT2));
    // --- program the IRQ into the 8259
    outp(PIC01, (inp(PIC01) & ComIRQ()));
    // --- enable interrupts
    outp(IntEnable(), DataReady);
    // --- end of interrupt
    outp(PIC00, EOI);
    // ----- flush any old interrupts
    inp(RxData());
    inp(IntIdent());
    inp(LineStatus());
    inp(ModemStatus());
}
// ---- fetch the next port and character from the capture buffer
char* CommPort::nextchar()
{
    if (buffercount == 0)
        return 0;
    if (mp_nextout == mp_recvbuff+buffersize)
        mp_nextout = mp_recvbuff;
    mp_nextout += 2;
    --buffercount;
    return mp_nextout-2;
}

Back to Article


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.