News

Universal Serial

USB-to-serial adapters are a viable way to get a little more life out of your serial code libraries.


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?





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

DrDobbs encourages readers to engage in spirited, healthy debate, including taking us to task. However, DrDobbs 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/SPAM. DrDobbs 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.
 

Features

  • Booting an Intel Architecture System, Part II: Advanced Initialization

    Once the processor is running and memory has been initialized, timers and devices must be started up and a memory map laid out. Only then, can the OS be loaded.

  • Booting an Intel Architecture System, Part I: Early Initialization

    The boot sequence today is far more complex than it was even a decade ago. Here's a detailed, low-level, step-by-step walkthrough of the boot up.

  • How Data Dependence Affects Performance

    Data dependence between statements is a straightjacket on the compiler's ability to optimize code for parallelism. So to get the maximum benefit from parallel code, data dependence must be carefully managed

  • The Intel Threading Building Blocks Flow Graph

    User feedback inspired the flow graph feature in Intel Threading Building Blocks, which allows programmers to express static and dynamic dependency graphs, as well as reactive or event-based graphs.

More Features >>

Go Parallel Video

Go Parallel Twitter Feed

More Twitter >>

Our Facebook Community