1

I am writing a program in C to communicate with an HP 7550 pen plotter over RS-232, and am having trouble getting flow control to work. When using a terminal program (such as TeraTerm) to send data to the plotter, XON/XOFF flow control works fine. However, when I attempt to send data using my program, the flow control is ignored and the plotter's (very small) buffer gets overflowed. I can see the XOFF character get trasmitted by the plotter by watching the LED on the USB <-> RS-232 adapter, but the PC ignores it and keeps sending data.

Here are the two functions that set up and open the serial port:

// Function openPort
//  - Opens a serial port with a port name provided in the format "\\\\.\\COM#"
HANDLE openPort(char *portName) {

    HANDLE serialPort;
    COMMTIMEOUTS timeouts = {0};    

    // open the port
    //printf("opening serial port . . . ");
    serialPort = CreateFile(portName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, NULL); // open the port
    if(serialPort == INVALID_HANDLE_VALUE) {
        //printf("failed\n");
        return NULL;
    } else
        //printf("ok\n");

    // set up timeouts
    //printf("Setting up timeouts . . . ");
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;

    if(!SetCommTimeouts(serialPort, &timeouts)) {
        //printf("failed - could not set timeouts\n");
        return NULL;
    }
    
    if(!SetCommMask(serialPort, EV_RXCHAR)) {
        //printf("failed - could not set comm mask\n");
        return NULL;
    }

    //printf("ok\n");
    return serialPort;
}

// Function setupPort
//  - Sets up the paramters of the serial port
bool setupPort(HANDLE *serialPort, int baudRate, int byteSize, int stopBits, char parityType[], char flowControl[]) {

    DCB dcb = {0};
    bool error = false;

    if(GetCommState(serialPort, &dcb)) {
        //printf("Setting up serial port parameters . . .\n");

        // basic setup parameters
        dcb.fBinary = true;
        dcb.fDtrControl = DTR_CONTROL_ENABLE;
        dcb.fDsrSensitivity = false;
        dcb.fTXContinueOnXoff = false;
        dcb.fOutX = false;
        dcb.fInX = false;
        dcb.fErrorChar = false;
        dcb.fNull = false;
        dcb.fRtsControl = RTS_CONTROL_ENABLE;
        dcb.fAbortOnError = false;
        dcb.fOutxCtsFlow = false;
        dcb.fOutxDsrFlow = false;

        // set up baud rate and byte size
        dcb.BaudRate = baudRate;
        dcb.ByteSize = byteSize;

        // set up stop bits
        //printf("Setting up stop bits . . . ");
        switch(stopBits) {
            case 1:
                dcb.StopBits = ONESTOPBIT;
                //printf("ok\n");
                break;
            case 2:
                dcb.StopBits = TWOSTOPBITS;
                //printf("ok\n");
                break;
            default:
                //printf("Invalid number of stop bits\n");
                error = true;
        }

        // set up parity
        //printf("Configuring %s parity . . . ", parityType);
        if(parityType == "NONE") {
            dcb.Parity = NOPARITY;
            //printf("ok\n");
        } else if(parityType == "ODD") {
            dcb.Parity = ODDPARITY;
            //printf("ok\n");
        } else if(parityType == "EVEN") {
            dcb.Parity = EVENPARITY;
            //printf("ok\n");
        } else if(parityType == "MARK") {
            dcb.Parity = MARKPARITY;
            //printf("ok\n");
        } else if(parityType == "SPACE") {
            dcb.Parity = SPACEPARITY;
            //printf("ok\n");
        } else {
            //printf("Invalid parity type\n");
            error = true;
        }

        // set up flow control
        //printf("Configuring %s flow control . . . ", flowControl);
        if(flowControl == "NONE") {
            //printf("ok\n");
        } else if(flowControl == "XONXOFF") {
            dcb.fOutX = true;
            dcb.fInX = true;
            //printf("ok\n");
        } else if(flowControl == "RTS/CTS") {
            dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
            dcb.fOutxCtsFlow = true;
            //printf("ok\n");
        } else if(flowControl == "DSR/DTR") {
            dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
            dcb.fOutxDsrFlow = true;
            //printf("ok\n");
        } else {
            //printf("Invaid flow control type\n");
            error = true;
        }
    }
    if(!SetCommState(serialPort, &dcb)) {
        error = true;
    }
    return error;
}

Here is the function that writes data to the serial port:

// Function writeData
//  - Writes an array of chars to the port
bool writeData(HANDLE serialPort, const char *data) {
    DWORD dataSize = strlen(data);
    DWORD bytesWritten;
    return WriteFile(serialPort, data, dataSize, &bytesWritten, NULL);
}

I am initializing the port in my main program in the following function:


// Function initSerial():
//   This function initializes the serial connection for the plotter to communicate
//   It takes five arguments:
//    - portNumber (1-255)
//    - baudRate (110, 220, 440, 680, 1200, 2400, 4800, or 9600 baud)
//    - dataBits (7 or 8)
//    - parity (NONE, ODD, EVEN, MARK, SPACE)
//    - flowControl(NONE, XONXOFF, RTS/CTS, DSR/DTR)
HANDLE initSerial(int portNumber, int baudRate, int dataBits, char* parity, char* flowControl) {
    char s[13];
    sprintf(s, "\\\\.\\COM%d", portNumber); // format COM port number
    HANDLE serialPort = openPort(s);
    setupPort(serialPort, baudRate, dataBits, 1, parity, flowControl);
    return serialPort;
}

and that function gets called using the following line:

HANDLE port = initSerial(12, 9600, 8, "NONE", "XONXOFF");

The rest of the program basically consists of repeated calls to writeData() with various HPGL commands to instruct the plotter what to draw.

I was under the impression that flow control was handled by the serial driver, hence why I specify "XONXOFF" flow control to Windows when I set up the port. I've tried XON/XOFF and RTS/CTS flow control with the same results. I have tried two different RS-232 adapters and two different PCs, one running Windows 7 Ultimate and the other Windows 10 Professional.

I realize my C code may not be particularly excellent, but hopefully it's clear enough to be readable.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • You don't check if your `setupPort` returns an error. Start there. Then you could also use TeraTerm and use that as "plotter" and manually send XOFFs from TeraTerm in order to make sure that your program stops sending characters. – Jabberwocky Aug 07 '23 at 08:38
  • Wild guess: check if XON/XOFF is supported by using [GetCommProperties](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcommproperties) – Jabberwocky Aug 07 '23 at 08:40
  • 2
    You should confirm that in the *dcb* structure the values of *XonChar* and *XoffChar* are set to the proper values (17 and 19). You could also try to use BuildCommDCB() function. I don't think that software flow control is in charge of the device driver. – linuxfan says Reinstate Monica Aug 08 '23 at 04:36
  • 1
    Can you reproduce it using [putty](https://github.com/github/putty/tree/7003b43963aef6cdf2841c2a882a684025f1d806)? – YangXiaoPo-MSFT Aug 09 '23 at 06:37
  • 3
    Your `setupPort` should always return an error because - unlike C# - comparing two strings using `==` or `!=` in C or Java will not do what you probably expect: In both languages (C and Java) the `==` (and `!=`) operator checks if the strings are stored at the same address in memory (what is typically not the case) and not if they contain the same text. To check if a string contains a certain text, you should use `strcmp` (or `stricmp` for a case-insensitive check): `if(strcmp(flowControl,"DSR/DTR")==0)` – Martin Rosenau Aug 09 '23 at 12:03
  • 1
    You don't check the open of the serial port, neither if there's some error on configuring. With this assumption, how can you guess if you have the port correctly configured. I'm not a windows expert to tell you more, so I cannot write an answer. Serve this comment as some hint for you to continue testing your code. – Luis Colorado Aug 09 '23 at 12:19
  • 2
    @MartinRosenau I completely missed that, I'd wager that's part of (if not all of) my problem! The program is split into several parts, the serial control part I wrote a while back when I was transitioning to C from C++, so I wasn't used to using `strcmp`. I'll rewrite the functions using `strcmp` and see if that solves my problem. This actually makes a lot of sense, because I was having trouble sending data to another serial device that required odd parity, but I could receive data just fine. I bet since my program was sending data with no parity, the device was ignoring it or throwing it out. – PatrioticStripey Aug 09 '23 at 13:14
  • @PatrioticStripey, Will you answer yourself? – YangXiaoPo-MSFT Aug 10 '23 at 05:31

0 Answers0