Link to home
Start Free TrialLog in
Avatar of john_ws
john_ws

asked on

Hardware Flow Control over RS232 (Serial) in DOS

I know the rules state that I'm not allowed to let you answer this question for me, since it's for school. But directing me in the right way would be more than helpful.

Basically I have a lab from last semester that is basic communication over serial using a defeated Null Modem Cable, but now we are given a full handshake cable (with CTS and RTS lines, etc) and must implement hardware flow control (and modify the old lab to allow this). What I know is that we must use CTS (Clear to Send), RTS (Request to Send), MCR (Modem Control Register), and MSR (Modem Status Register) to accomplish this.

It is surprisingly difficult to find examples in C (especially for DOS) of how exactly to do this. We were never actually taught this and it was one of those "research this on your own time" type of things.

If it helps, here is the code from last semester, just to give you an idea of what functions and includes we are using (conio.h, outportb(), inportb(), etc). WARNING: LONG CODE UP AHEAD!

====================================================

 /* Included Libraries */

#include <conio.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/* Key Defines (ESC, F1, F2, F3, F4, F5) */

#define KEY_ESC       27
#define KEY_F1             59
#define KEY_F2             60
#define KEY_F3             61
#define KEY_F4             62
#define KEY_F5             63
#define KEY_F6            64
#define KEY_F7            65

/* Defines */

// COM1 0x3F8
// COM2 0x2F8
// COM3 0x3E8
// COM4 0x2E8

#define WALK_SIZE       80    /* Length of walking string   */
#define MAX_STRING      32    /* Max lengt of input strings */
#define BUFFER_SIZE       1024  /* Max buffer size for rx in  */

#define S_PORT             0x3f8 /* Serial port  */
#define INTMASK       0x21  /* Interrupt Mask */
#define INTVECT            0x0C  /* Interrupt Vector */

#define IER             ( S_PORT + 1 ) /* Interrupt Enable Register */
#define IIR             ( S_PORT + 2 ) /* Interrupt ID Register     */
#define LCR             ( S_PORT + 3 ) /* Line Control Register     */
#define MCR             ( S_PORT + 4 ) /* Modem Control Register    */
#define LSR             ( S_PORT + 5 ) /* Line Status Register      */
#define MSR             ( S_PORT + 6 ) /* Modem Status Register     */
#define DLAB_LOW       ( S_PORT + 0 ) /* DLAB Low Bit              */
#define DLAB_HIGH       ( S_PORT + 1 ) /* DLAB High Bit             */
#define DLAB             0x80  /* Divisor Latch Access Bit */

#define SBITS1             0x00  /* Stop Bits (1)       */
#define SBITS2             0x04  /* Stop Bits (2)       */

#define NBITS7             0x02  /* Data Bits (7)       */
#define NBITS8             0x03  /* Data Bits (8)       */

#define EVENPARITY       0x18  /* Parity Bits (EVEN)  */
#define NOPARITY       0x00  /* Parity Bits (NONE)  */
#define ODDPARITY       0x08  /* Parity Bits (ODD)   */
#define SPACEPARITY       0x38  /* Parity Bits (SPACE) */
#define MARKPARITY       0x28  /* Parity Bits (MARK)  */

#define BR37      0xC00       /* Baud Rate (37)      */
#define BR75      0x600       /* Baud Rate (75)      */
#define BR150     0x300       /* Baud Rate (150)     */
#define BR300         0x180       /* Baud Rate (300)     */
#define BR600         0xC0        /* Baud Rate (600)     */
#define BR1200         0x60        /* Baud Rate (1200)    */
#define BR2400         0x30        /* Baud Rate (2400)    */
#define BR4800         0x18        /* Baud Rate (4800)    */
#define BR9600         0x0C        /* Baud Rate (9600)    */
#define BR19200   0x06        /* Baud Rate (19200)   */
#define BR38400   0x03        /* Baud Rate (38400)   */
#define BR57600   0x02        /* Baud Rate (57600)   */
#define BR115200  0x01        /* Baud Rate (115200)  */

/* Start of Global Variables */

int             bufferpos,      /* Keep track of current buffer position. */
            currentbuf;

char             receivebuffer   [ BUFFER_SIZE ], /* Receive Buffer.        */
            argument       [ MAX_STRING ],       /* Command line arguments */
            setting        [ MAX_STRING ],  /* Left side of a=b arg.  */
            value              [ MAX_STRING ];  /* Right side of a=b arg. */

unsigned long int baudrate       = BR2400,       /* Default Baud value     */
              numbits        = NBITS8,       /* Default Data bit value */
              numstopbits       = SBITS1,        /* Default Stop bit value */
              parity      = ODDPARITY;       /* Default Parity value   */

/* Function Prototypes */

void interrupt rxchar ( void );
void interrupt ( * old_int ) ( );

void parse_string ( void );
void parse_params ( void );
void walk_pattern ( void );

/* Our Main */
int main ( int argc, char *argv[] )
{
      int i;
      char chartoprint, txchar;

      printf ( "\n" );

      /* If there is at least one arguments from the command line, *
       * and no more than 4, then let's parse them.                */
      if ( argc >= 2 && argc <= 5 )
      {
            for ( i=1; i < argc; i ++ )
            {
                  /* Copy each arg to "argument", and then first     *
                   * parse the string to get the setting and value.  *
                   * Then pass both to parse_params which takes care *
                   * of all the input parameters.                       */
                  strcpy ( argument, argv[i] );
                  parse_string ( );
                  parse_params ( );
            }
      }

      /* Otherwise, there are no parameters, or too many. So just use     *
       * default values.                                        */
      else
      {
            printf ( "\n" );
            printf ( "No parameters were entered.\n" );
            printf ( "Using default.\n\n" );
      }

      /* Print of the Hex values of all the Serial Port values */
      printf ( "Baud Rate:      0x%x\n", baudrate );
      printf ( "Parity:         0x%x\n", parity );
      printf ( "Stop Bits:      0x%x\n", numstopbits );
      printf ( "Number of Bits: 0x%x\n\n", numbits );

      disable ( ); /* Disable interrupts while we prepare the serial port. */

      /* Preparing the serial ports */
      outportb ( IER, 0x00 );

      old_int = getvect ( INTVECT );
        setvect ( INTVECT, rxchar );

      outportb ( LCR, DLAB );
      outportb ( DLAB_LOW, baudrate );
      outportb ( DLAB_HIGH, 0x00 );
      outportb ( LCR, numbits | parity | numstopbits );
      outportb ( IIR, 0xC7 );
      outportb ( MCR, 0x0B );
      outportb ( INTMASK, ( inportb ( INTMASK ) ) &0xEF );
      outportb ( IER, 0x01 );

      enable ( ); /* Renabling interrupts since we're done preparing */

      do
      {
            //rxchar (); /* Polling the serial ports for new characters */


            delay ( 10 );

            /* If the buffer position that has been read, does not    *
             * equal the current buffer position (of the available    *
             * read in buffer), then we have to read it in.           */
            if ( bufferpos != currentbuf )
            {
                  /* Get the character to print from the buffer */
                  chartoprint = receivebuffer [ currentbuf ];

                  /* Increase the buffer position */
                  currentbuf++;

                  /* If the position is over the buffer size, wrap around */
                  if ( currentbuf >= BUFFER_SIZE ) currentbuf = 0;

                  /* If the host has sent the F5 key, they are requesting *
                   * to clear our screen. So let's clear it.              */
                  if ( chartoprint == KEY_F5 ) system ( "cls" );

                  else printf ( "%c", chartoprint );

            }

            /* If there is a character being pressed on the keyboard .. */
            if ( kbhit ( ) )
            {
                  txchar = getch ( );

                  switch ( txchar )
                  {
                        case KEY_F1: walk_pattern (); break;
                        case KEY_F2: disable (); break;
                        case KEY_F3: enable (); break;
                        case KEY_F4: system ("cls"); break;
                        case KEY_ESC: break;

                        default: outportb(S_PORT, txchar);
                  }
            }
      } while ( txchar != KEY_ESC );

      outportb ( IER, 0 );
      outportb ( INTMASK, ( inportb ( INTMASK ) | 0x10 ) );
      setvect ( INTVECT, old_int );

      return 0;
}


/* Send 80 characters of our walking pattern. */
void walk_pattern ( )
{
      int thischar = 'A', i;

      for ( i = 0; i < WALK_SIZE; i ++ )
      {
            outportb ( S_PORT, thischar );
            thischar ++;
      }

      return;
}


/* Parse the args that were passed in from the command line
 * that were in the format setting=value. */
void parse_string ( )
{
      int counter1 = 0, counter2 = 0;

      while ( ( argument[counter1] != '=' ) && ( argument[counter1] != '\0' ) )
      {
            setting [counter1] = argument [counter1];
            counter1++;
      }

      setting [counter1] = '\0';
      counter1 ++;

      while ( argument [counter1] != '\0' )
      {
            value [counter2] = argument [counter1];
            counter1 ++;
            counter2 ++;
      }

      value [counter2] = '\0';
}


/* After we have parsed the paramters from the command line, we can
 * now get these values and place them in our serial port parameter
 * variables */
void parse_params ()
{
      long int baud_in,
             stop_in,
             parity_in,
             bytes_in;

      if ( !strcmp ( setting, "baud" ) )
      {
            baud_in = atoi(value);

            if ( baud_in == 37 ) baudrate = BR37;
            else if ( baud_in == 75    ) baudrate = BR75;
            else if ( baud_in == 150   ) baudrate = BR150;
            else if ( baud_in == 300   ) baudrate = BR300;
            else if ( baud_in == 600   ) baudrate = BR600;
            else if ( baud_in == 1200  ) baudrate = BR1200;
            else if ( baud_in == 2400  ) baudrate = BR2400;
            else if ( baud_in == 4800  ) baudrate = BR4800;
            else if ( baud_in == 9600  ) baudrate = BR9600;
            else if ( baud_in == 19200 ) baudrate = BR19200;
            else if ( baud_in == 38400 ) baudrate = BR38400;
            else baudrate = BR2400;
      }

      if ( !strcmp ( setting, "stop") )
      {
            stop_in = atoi ( value );
            if ( stop_in == 2 ) numstopbits = SBITS2;
            else numstopbits = SBITS1;
      }

      if ( !strcmp (setting, "bits") )
      {
            bytes_in = atoi ( value );
            if ( bytes_in == 7 ) numbits = NBITS7;
            else numbits = NBITS8;

      }
      if ( !strcmp(setting, "parity" ) )
      {
            if ( !strcmp ( value, "even" ) ) parity = EVENPARITY;
            else if ( !strcmp ( value, "none" ) ) parity = NOPARITY;
            else if ( !strcmp ( value, "space" ) ) parity = SPACEPARITY;
            else if ( !strcmp ( value, "mark"  ) ) parity = MARKPARITY;
            else parity = ODDPARITY;
      }
}

/* Polling the serial port for new available data */
void interrupt rxchar()
{
      int checkport = inportb ( LSR );

      while ( checkport & 1 )
      {
            if ( bufferpos >= BUFFER_SIZE ) bufferpos = 0;
            receivebuffer [bufferpos] = inportb ( S_PORT );
            bufferpos ++;
            outportb ( 0x20 , 0x20 );
            checkport = inportb ( LSR );
      }
}

====================================================


I know the parsing and stuff is pretty ugly (using global variables and all). This was actually modified from basic template we were given, so don't blame that on me :P

Basically all this does is allows you to enter certain baudrates and parity bits etc, if you want to. If you don't, it will use a default value. Then it will communicate with the other machine over COM1 using the Null Modem cable.

What I'm asking is what type of code would I have to add to implement hardware flow control? I am terrible with this communication stuff, so any examples or guides you can direct me to would be great.

Thank you.
Avatar of john_ws
john_ws

ASKER

It probably doesn't matter, but I'm using the DOS compiler: Borland C++ Version 3.1 (aka "BC").
You want to look at the modem register that controls the Clear To Send bit.  You want to set this line HIGH to start with, then if your input buffer is getting close to filling up, drop that line.

On the sending end, before you send each byte, look at the status of the line.  If it's low you want to wait until it goes back high.

Avatar of john_ws

ASKER

Thank you!

Hmm..

On the Modem Control Register, Bit 1 is "Force Request to Send", Does that mean if I do the following:

outportb ( MCR, ( inportb ( MCR ) | 0x02 ); // setting bit 1 "HIGH"

It will activate CTS to the other computer?

Because the documentation I'm reading says that bit 4 on the MSR is "Clear to Send"

So I was just testing and did the following:

if ( inportb ( MSR ) & 0x10 ) // True if bit 4 is "HIGH"

But it never returns true. And MSR never seems to change on the other computer, no matter I set MCR to on this computer.

Am I missing something?
Request to send (RTS) is connected to Clear to send (CTS). When one side raises RTS, the other side gets a CTS and can start sending data.

One thing I have learned in serial communications is that you need to have a breakout box. You can get those at a Radio Shack or pretty much any electronics place that sells connectors. They not only have lights that tell you when a signal is on, but they have jumpers so that you can turn on a signal so you can see its results in your code.

I would set up a breakout box and set up a function key to toggle the RTS line. if you see it changing, then put the breakout box at the other end of the cable and see what that shows.

There are a couple of potential problems: One is that the cable is a bad cable. That deosn't happen very often, but can drive you crazy when it does. The other thing, is that the the NULL modem cable may not cross the RTS/CTS correctly.

If you have a scope, you can test the pins on the chip or look at each rs232 line. Pt breakout boxes are easir to come by and much easier to use.

Bill
Avatar of john_ws

ASKER

Thank you Bill,

Since I have quite a few of these serial labs this semester, maybe I will invest in a "Breakout box."

Just a  question:

I opened Hyper Terminal on both computers and connected them both to COM1 with default settings (With "Hardware Flow Control"). They actually do communicate properly when I type in the console screen. (Type on computer A, shows up on computer B, and vice versa). Does this verify that my cable is wired properly?
Hi john_ws,

>>Does this verify that my cable is wired properly?
It certainly should but it may not. As you are probably aware, a defeated modem cable would achieve the same. But its a good indicator.

Essentially, since almost all the lines are crossed over on a null-modem cable you will have to get your head around the following:

Raising DTR at one end will (usually) raise DSR at the other and sometimes DCD.
Raising RTS at one end will raise CTS at the other.
Data sent on TxD at one end will arrive at the other on RxD.

Good luck.

Paul
ASKER CERTIFIED SOLUTION
Avatar of billtouch
billtouch
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of john_ws

ASKER

Ok it appears as my RTS/CTS lines on one of the computers arent working properly. It might be because of one of those machines is using a USB to serial adapter (a cheap one off eBay). Thank you all for the information though, it was very helpful.
Yep, get  a breakout box, it reveals things in a second that otherwise you'll never find.

AND make sure you know how the cable is wired up-- get an ohmmeter and trace out each pin.  Many cables flip things around and tie back the handshaking lines, effectively disabling them.

And using a USB seral port is just adding another complication-- there's no guarantee the USB port and its software driver will faithfully use the handshaking lines.   Toss that away.

>>Toss that away.
Weeell, not quite the best advice but certainly get it out of your face while you are debugging. If you get it working against a normal RS232/422 port then move to the USB and see if it still works. If not, and a USB connection is required, at least you know it SHOULD work. I'm sure that's what grg99 meant :-)

Paul
No, you should toss it away.

You see there's waaay tooooo many levels of fooling going on:

(1)  You're running a program in a DOS window on Windows XP.
(2)  Your program does a "OUT $279,$40"  let's say this is the modem control register
(3)  The CPU chokes on the out instruction and traps to the kernel.
(4)  The kernel sees you're running in virtual 8086 mode, so it hands the CPU off to the "bad instruction" interrupt handler.
(5)  The BIIH sees you're in a DOS box, so MAYBE the OUT instruction needs to be emulated.
(6)  It sees if there's an OUT emulation handler registered for port $279.    Yep, there is.
(6)  The 279 emulator recognizes this is one of its ports, the modem status register, so it tries to do the right thing.
(7)  It looks and sees if COM1 is implemented as a Windows XP driver, and by golly it is.  
(8)  It does the proper IOCTL() function to call the driver to twiddle that register.
(9) But it turns out the XP driver has been overriden by a USB filter driver.
(10) The USB filter driver notices it's been asked to change a modem control line, so it calls the USB IOCTL driver.
(11)  The USB IOCTL driver sends the right packet to the right USB port to change the bit.
(12)  The chip might or might not change the right signal line, maybe at the right time, or maybe out of step with the data.
(13) The drivers all return, the kernel returns to your program, many thousands of instructions later.

.... of course, if you try twiddling some of the more complicated serial register bits, like the FIFO test or control bits, things are likely to get mangled at each and every step above.

USB > RS232 adapters sometimes work and sometimes don't. I went through 4 different brands to find one that did as advertised. I experienced the same issues when looking for an RS422 adapter.

On all USB ports, the  control lines are emulated. You will note that in the following USB pinout, there are no control lines.

Pin Name Description Cable Colour
1 Vcc +5V DC Red
2 D- Data -  White
3 D+ Data + Green
4 Gnd Ground Black

The control signals are created via a combination of jumpers and a built-in algororithm. Unless you are prepared to work your way through all that to achieve results, I would put forth the effort to find a real rs232 port.

Then going along with what grg99 said, try to find a MSDOS boot disk. This will give you direct control of the hardware. Just make sure the you have a fat32 partition to put your executable on. MSDOS does not recognize NTFS. Or you could just write them to a floppy. That is fat16 and recognized by all MS OSs.

Bill