Channels ▼

Al Williams

Dr. Dobb's Bloggers

Universal Serial

February 20, 2012

If you are a science fiction fan like I am, you may have seen some of the old "serial" movies like Flash Gordon or Buck Rogers (both from Universal Pictures). Every week, the hero would appear to have reached an impossible situation and was facing certain doom at the movie's end. Next week, the hero would somehow escape and continue on to new adventures until the end when he'd face another sure death. Apparently, in silent movies it was common for the hero to hang over the edge of a cliff, and thus this type of ending is called a "cliffhanger."

Last week, I was talking about a different kind of serial — a USB serial port. I used Qt to communicate with a pulse output device (actually a PIC running special firmware). Qt is cross-platform, but unfortunately it doesn't support the serial port. So while the software ought to run anywhere Qt can handle, the serial port code was strictly Linux only.

However, the code used a simple abstraction layer to communicate with the serial port consisting of just a few calls (open, close, read, write, and ready). By replacing these simple calls with Windows-specific code, the software can run under Windows with a simple recompile.

If you recall, I borrowed the Linux serial code from another project named the GP3, and the same project provided the Windows code. Just like the Linux side, I placed the code in a C++ file even though it's straight C. I also protected it with QT's macro that indicates the target platform is Windows:


#include <QApplication>
#if defined(Q_OS_WIN32)

The Windows code really isn't that much different from the Linux code. It simply uses different names for the same concepts. Here's the open code, for example:


int gp3openport(const char *port)
{
        DCB dcb;
        COMMTIMEOUTS cto;
    DWORD rv;
    size_t count;
    wchar_t wport[64]=L"\\\\.\\";
    mbstowcs_s(&count,wport+4,sizeof(wport)/sizeof(wchar_t)-4,port,_TRUNCATE);
    comport=CreateFile(wport,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,
     FILE_ATTRIBUTE_NORMAL,NULL);
    if (comport!=INVALID_HANDLE_VALUE)
        {
           SetupComm(comport,128,128);
           cto.ReadIntervalTimeout=0;
           cto.ReadTotalTimeoutMultiplier=100;
           cto.ReadTotalTimeoutConstant=0;
           cto.WriteTotalTimeoutMultiplier=0;
           cto.WriteTotalTimeoutConstant=100;
           SetCommTimeouts(comport,&cto);
           dcb.DCBlength=sizeof(dcb);
           GetCommState(comport,&dcb);
           rv=BuildCommDCB(TEXT("baud=9600 parity=N data=8 stop=1"),&dcb);
           dcb.fBinary=TRUE;
           dcb.fDtrControl=DTR_CONTROL_DISABLE;
           dcb.fOutxDsrFlow=FALSE;
           dcb.fNull=FALSE;
           dcb.fRtsControl=RTS_CONTROL_ENABLE;
           dcb.fOutxCtsFlow=TRUE;
           dcb.fDsrSensitivity=FALSE;
           dcb.fOutX=FALSE;
           dcb.fInX=FALSE;
           rv=SetCommState(comport,&dcb);
           if (!rv) 
           {
                   CloseHandle(comport);
                   comport=INVALID_HANDLE_VALUE;
           }
        }

The CreateFile call corresponds to an open call on Linux. For low-numbered COM ports, you can simply use the name (like COM1). But this code is meant for a USB COM port and these could have high port numbers. In that case, the correct syntax is \\.\COM12 (or whatever). Although this syntax is required for large port numbers, it works for any port number so the code simply appends this prefix to the name before calling CreateFile.

After the port is open, calls to SetupComm and SetCommTimeouts set the buffer sizes and the timeout behavior, respectively. The DCB (Device Control Block) is how you configure the baud rate and other parameters. Once it is filled out, the SetCommState call makes the change.

The rest of the code is wholly anti-climatic. ReadFile, WriteFile, and CloseHandle do all the work. You can find the source code online.

Probably the trickiest part of the whole thing is providing the drop down with the port names in the user interface. On Linux it is easy to just scan the /dev directory for this pattern:

 

tty[SU]*

The Qt QDir object makes that very simple.

Windows isn't quite so easy. If you really wanted to do it right, you can scan the Windows registry under HARDWARE\DEVICEMAP\SERIALCOMM and learn the serial port names. I decided it was easier to just set the combo box to hold COM1, COM2, COM3, and COM4. The combo box allows you to type anything you want in it, so if you have COM20 or something you just have to type it in. Once you enter a port, the program will remember it in the future.


    // populate COM ports
#if !defined(Q_OS_WIN)
    // note System needed for Dev files!
    QDir dev("/dev","tty[SU]*",QDir::Name,QDir::Files|QDir::Readable|QDir::Writable|QDir::System);
    QStringList devices;
    devices=dev.entryList();
    ui->comPort->addItems(devices);
#else
    // just stick a few in, the user can edit if needed
    ui->comPort->addItem("COM1");
    ui->comPort->addItem("COM2");
    ui->comPort->addItem("COM3");
    ui->comPort->addItem("COM4");
#endif
// get saved port
    QSettings set("awce.com","gp9demo");
#if defined(Q_OS_WIN)
    QString portsel=set.value("option/port","COM1").toString();
#else
    QString portsel=set.value("option/port","ttyS0").toString();
#endif
    // if we have one make sure it is in the list and select it
    if (!portsel.isEmpty())
    {
        int idx;
        idx=ui->comPort->findText(portsel);
        if (idx<0)
        {
            ui->comPort->addItem(portsel);
            idx=ui->comPort->findText(portsel);
        }
        if (idx>=0) ui->comPort->setCurrentIndex(idx);
    }

You might wonder what other cross-platform options are available. Java had fair support for serial ports, but there was little official support for it after it was introduced. However, the RXTX library provides the same API and is actively developed. Some time ago, I was pleasantly surprised to find that some C# code I wrote under Windows worked great using Mono (including the serial port) as long as I allowed the user to enter port names like /dev/ttyS0 instead of COM1.

The moral of the story is that USB-to-serial adapters are a viable way to get a little more life out of your serial code libraries. The next step, of course, is to figure out how to actually make "real" USB devices. How's that for a cliff hanger?

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.
 


Video