
                            Modem programming guide
                                       
The Atari ST modem programming guide
October 12, 1991

Copyright 1991 Steve Yelvington
        Internet: <steve@thelake.mn.org>
        GEnie:    S.YELVINGTO2


Introduction
------------

Modem i/o on the ST is isn't difficult. In fact, compared with other
personal systems, the ST is unusually easy to program for serial
communications.

The hard work is done for you. TOS includes interrupt-driven serial
port routines. This means that when a character arrives at the serial
port, TOS breaks away from whatever it's doing, grabs the character,
stores it in a buffer, and returns to the task that was interrupted.
You don't need to write code to do that.

The serial port buffers -- one for input, one for output -- are FIFO
(First In, First Out) queues. TOS provides functions for reading from
the input buffer, writing to the output buffer, and even changing the
size of the buffers.

All you have to do is read from or write to the serial port as if it
were a console.

This document contains examples in C. If you're programming in another
language, don't despair, because C is used here only to string together
TOS functions. The strategies don't change when you shift to another
system of notation.

The modem i/o routines
----------------------

Both BIOS and GEMDOS provide functions for single-character serial
i/o that work exactly like their console equivalents.

>From GEMDOS:

   Console      Serial         Description
   -------      ------         -----------
   Cconin                      Read a character with echo
   Cnecin       Cauxin         Read a character without echo
   Cconout      Cauxout        Write a character
   Cconis       Cauxis         Return TRUE if a character is available
   Cconos       Cauxos         Return TRUE if output is possible, i.e.,
                               the output buffer has room for more data

There is not a serial port equivalent for Cconws, but you should be
able to write one easily enough:

#include <osbind.h>
void Cauxws(s) /* write a nul-terminated string to the aux port */
        register char *s;
        {
        register char c;
        while (c=*s++)
                Cauxout(c);
        }


In C, GEMDOS functions usually are implemented as preprocessor
macros that translate into gemdos() function calls. Other
languages may require that you write GEMDOS calls explicitly. If
you're coding in assembler, you should be able to translate the
following by keeping in mind that you push the arguments onto
the stack from right to left and then call trap #1 for gemdos.

Here are common C preprocessor definitions for the
above-mentioned operations (plus a few more).

#define Cconin()        (long)  gemdos(0x1)
#define Cconout(a)      (void)  gemdos(0x2,(short)(a))
#define Cauxin()        (short) gemdos(0x3)
#define Cauxout(a)      (void)  gemdos(0x4,(short)(a))
#define Cprnout(a)      (void)  gemdos(0x5,(short)(a))
#define Cnecin()        (long)  gemdos(0x8)
#define Crawio(a)       (long)  gemdos(0x6,(short)(a))
#define Crawcin()       (long)  gemdos(0x7)
#define Cconws(a)       (void)  gemdos(0x9,(char*)(a))
#define Cconrs(a)       (void)  gemdos(0x0a,(char*)(a))
#define Cconis()        (short) gemdos(0x0b)
#define Cconos()        (short) gemdos(0x10)
#define Cprnos()        (short) gemdos(0x11)
#define Cauxis()        (short) gemdos(0x12)
#define Cauxos()        (short) gemdos(0x13)


What about BIOS?

>From BIOS, the approach is slightly different. BIOS provides one
function for each operation, with the device indicated by an
argument. The first argument to the function is the device
number:

   Console      Serial         Description
   -------      ------         -----------
   Bconin(2)    Bconin(1)      Read a character
   Bconout(2,c) Bconout(1,c)   Write a character c
   Bconstat(2)  Bconstat(1)    Return TRUE if a character is available
   Bcostat(2)   Bcostat(1)     Return TRUE if output is possible

Note that the device numbers used by the BIOS are different from the
file handles used at the GEMDOS level. The BIOS device numbers may be
defined in this fashion:

#define BCON_PRT        0 /* printer */
#define BCON_AUX        1 /* aux (modem) port */
#define BCON_CON        2 /* console */
#define BCON_MIDI       3 /* MIDI port */
#define BCON_KBD        4 /* intelligent keyboard device output */
#define BCON_RAW        5 /* raw console output, no VT52 */

The BIOS macros are defined as follows:

#define Bconstat(dev)   bios(1,(short)(dev))
#define Bconin(dev)     bios(2,(short)(dev))
#define Bconout(dev,ch) bios(3,(short)(dev),(short)(ch))
#define Bcostat(dev)    bios(8,(short)(dev))

If you're using assembly language, the bios trap is #13.

There is a no function to read a string from the serial port at
either the GEMDOS level or the BIOS level. For reasons that will
become clear shortly, you should write your own.

All of the GEMDOS and BIOS input functions return 32-bit LONG values.

In the case of the serial port, only the low 8 bits are interesting.

In the case of the console, the low 8 bits will contain the
ASCII code corresponding to the key -- if there is one. If the
keystroke is a nonstandard key, such as a function key, HELP or
UNDO, the low eight bits will be an ASCII nul value. In all
cases, the upper 24 bits contain a unique keyscan code and
information regarding the state of the shift keys.

A simple terminal program
-------------------------

A ``dumb'' terminal program using these functions is simple to write.
All you need is a loop in which you will ``poll'' the console and the
serial port. It might look like this:

#include <osbind.h>
#define BANNER "\033EIncredibly dumb full-duplex terminal version 0.1\r\n"

main()
        {
        int c;
        Cconws(BANNER);
        for(;;)
                {
                if (Cauxstat()) /* data waiting at the aux port */
                        {
                        c = (int) (Cauxin() & 0xFF); /* get it and clean it */
                        Cconout(c);
                        }
                if (Cconstat()) /* data waiting at the console */
                        {
                        c = (int) (Cnecin() & 0xFF); /* get it and clean it */
                        if (c) /* if it's not a NUL from a function key */
                                Cauxout(c);
                        }
                }
        }

You'll notice that the above example runs forever. Well, not quite.
GEMDOS console i/o processes control-characters, and a control-C will
kill the program.

Of course, that means you can't send control-C out the modem port.
That's one of many reasons it's called an ``incredibly dumb''
terminal.

You'll probably want to rewrite the program using BIOS functions and
test for use of the UNDO or F10 keys.


Whatever happened to GEM?
-------------------------

None of this discussion has dealt with GEM. Why? Because so far as
GEM is concerned, the serial port doesn't exist.

The AES Event Manager doesn't poll the aux port to determine whether
an event has occurred.

If you want to write a GEM event-driven communications program that
will cooperate with desk accessories for multitasking, you'll need to
use the timer with a time value of 0. This will allow a task switch
to take place, and when you regain control you can check the serial
port's status with GEMDOS or BIOS.

If you're worried that the serial input buffer will overflow while
some system-hog task is running, resize the buffer as described
below.

The real world intrudes
-----------------------

None of the above functions cares whether the modem is live or dead.
They only deal with data. If your caller falls asleep at the keyboard
or abruptly hangs up, the above functions won't worry about a thing.
If you're writing a communications program or a BBS, you'll have to
deal with these real-world conditions. Modems and phone lines are not
perfect.

There are many ways to handle such errors.  In C, you might want to
use the setjmp and longjmp functions to ``bail out'' and restart your
program. Assuming you have set up a jump buffer called ``panic,'' you
might write a low-level aux port input function that looks like this:

#include <time.h>
#include <osbind.h>
#define TIMEOUT         60      /* 60-second timeout */

int auxin()
        {
        clock_t time1,elapsed;
        if (!rs232cd())
                {
                hangup();
                longjmp(panic);
                }
        time1 = clock();  /* get tick time */

        /* loop for up to TIMEOUT seconds, waiting for aux input */
        for(;;)
                {
                if (Cauxis()) /* these could just as well be BIOS calls */
                        return Cauxin();

                /* no activity yet, so ... */
                /* get new tick time and convert to seconds */

                elapsed = (clock() - time1) / CLK_TCK;

                /* if too much time has elapsed, restart */

                if (elapsed > TIMEOUT)
                        {
                        hangup();
                        longjmp(panic);
                        }
                } /* continue the loop */
        }


The above code has used a couple of functions that aren't in the
library and bear some explaining. Here is the function that checks
the carrier-detect status of the aux port by examining a hardware
register:

#include <osbind.h>
int rs232cd()                   /* state of rs232 carrier detect line */
        {
        register long ssp;
        register int *mfp, status;
        mfp = ((int *) 0xFFFFFA00L);            /* base address of MFP */
        ssp = Super(0L);                        /* enter supervisor mode */
        status = *mfp;                          /* get MFP status */
        Super(ssp);                             /* return to user mode */
        return(!(status & 0x0002));             /* check for carrier */
        }


Here is a function that hangs up the phone line by dropping the DTR
(Data Terminal Ready) signal. Note that most modems are shipped from
the factory with DTR sensitivity disabled. You should be able to turn
it back on by toggling a DIP switch or with a command from the
keyboard.


#include <osbind.h>
void hangup()
        {
        Ongibit(0x10);  /* dtr off */
        sleep(1);       /* wait one second */
        Offgibit(0xEF); /* dtr on */
        }

Now, what about getting a string from the modem port? You'll need to
call your custom auxin() function, as above. Here is an auxgets
function modified from the dLibs getln. Note that it uses a function
``auxput'' for output; you can rewrite or #define it to mirror the
BIOS or GEMDOS-level routine you used for input:

#define KEY_CR          0x0D /* carriage return */
#define KEY_LF          0x0A /* linefeed */
#define KEY_BS          0x08 /* backspace */
#define KEY_DEL         0x7F /* delete */

char *auxgets(buffer, limit)
        char *buffer; /* where keystrokes will be stored */
        register int limit; /* size of the buffer */
        {
        register char *bp = buffer;
        register int c, i = 0;
        for(;;)
                {
                c = auxin() & 0xFF; /* mask off high bits, just in case */

                /* end of line */
                if((c == KEY_CR) || (c == KEY_LF))
                        {
                        *bp = '\0';
                        break;
                        }

                /* backspace or delete */
                else if(((c == KEY_BS) || (c == KEY_DEL)) && (bp != buffer))
                        {
                        --bp;
                        auxput('\b');
                        auxput(' ');
                        auxput('\b');
                        --i;
                        }
                else if((c >= ' ') && (i < limit))
                        {
                        auxput(*bp++ = c);
                        ++i;
                        }
                }
        return(buffer);
        }



Configuring the serial port
---------------------------

In order to communicate with another machine over the serial port,
speed and flow-control settings may need to be altered. Atari's XBIOS
(Extended BIOS) provides a function that can configure the serial
port. The function Rsconf sets the speed, flow-control and four
bitmapped hardware registers.

Speed (bits per second, often incorrectly called baud) can be set to
any of 16 values:

#define BPS_19200       0 /* standard */
#define BPS_9600        1 /* standard */
#define BPS_4800        2 /* standard */
#define BPS_3600        3
#define BPS_2400        4 /* standard */
#define BPS_2000        5
#define BPS_1800        6
#define BPS_1200        7 /* standard */
#define BPS_600         8
#define BPS_300         9 /* standard */
#define BPS_200         10
#define BPS_150         11
#define BPS_134         12
#define BPS_110         13
#define BPS_75          14
#define BPS_50          15

Only the values marked /* standard */ are commonly used in modem
communications.

If you are using a slow- or medium-speed modem (under 9600bps) you
probably will need to match the ST's serial port speed to that of the
remote system.

If you are using a high-speed modem, you may be able to set the speed
at 19,200bps and let the modem handle speed-matching chores. In this
case, you probably will need to use RTS/CTS (hardware) flow-control
to avoid overloading the ST's input buffer and the modem's output
buffer. RTS/CTS is broken in most versions of TOS, and an /auto/
folder program must be installed to fix it.

Flow control can be configured to any of four settings:

#define FLOW_NONE       0 /* default */
#define FLOW_XONXOFF    1 /* control-s, control-q software */
#define FLOW_RTSCTS     2 /* hardware */
#define FLOW_BOTH       3 /* both hardware and software */

For binary file transfers, software flow control must not be used.

The hardware registers are not useful in ordinary modem-related
programming, and altering their values incorrectly will have
unpleasant side effects. Passing -1 to Rsconf will leave the
corresponding setting unchanged.

Thus, to set bps rate to 2400 and flow control to none, the Rsconf
function call would look like this:

        Rsconf(BPS_1200,FLOW_NONE,-1,-1,-1,-1)


Parity and duplex
-----------------

Parity is not commonly used in communicating with personal computers,
but you may need to deal with it when communicating with a mainframe.

The idea behind parity is to provide at least a small degree of
assurance that the transmitted character is the same as the received
character. The way this is done is by fiddling with the high bit of
each byte, which ordinarily would be zero for an ASCII character.

``Even parity'' means that the high bit is either set or unset, as
necessary, to ensure that the result is an even number. ``Odd
parity'' means the opposite. You can test this with the modulus
operator in C, then strip the high bit by ANDing the byte with 0x7F
or set the high bit by ANDing with 0xFF.

TOS also can generate parity values automatically if the proper
hardware register is set. However, setting the register requires that
you also worry about start/stop bits, word length and the serial
hardware frequency, which is a can of worms.

Duplex is a potentially confusing term that describes whether you or
a remote system is responsible for putting a character on your
screen.

Most bulletin board systems run ``full duplex,'' which means that a
keystroke from a caller is sent to the BBS, which then echoes that
character back to the caller. This relationship is not symmetrical.
The caller doesn't echo anything back to the BBS, because the echoes
would be interpreted as commands.

``Half duplex'' means that each party is responsible for managing his
or her own display. GEnie normally runs half duplex. Neither caller
nor BBS echoes anything.

If you're writing a BBS or a terminal program with a ``chat mode,''
you may want to include a ``both local and remote echo'' option. This
would allow the remote to run full duplex while you manage both your
screen and the caller's screen.


Resizing the serial buffers
---------------------------

When a character arrives at the serial port, TOS interrupts what
it's doing, grabs the character before it disappears, and
records it in an input buffer. When your program wishes to write
to the serial port, TOS buffers the output so that your program
can proceed with other chores without having to wait for a slow
modem to transmit the data.

TOS provides those buffers, but TOS also provides a facility for
changing them. Why would you want to do so? Here's a scenario:

You're using Xmodem and you have a slow floppy drive. Xmodem
ACKS a block, then proceeds to write it to disk while the sender
sends the next block. So far, so good. But if you switch to
Ymodem's 1K blocks and a very fast modem, the TOS default buffer
might overflow while you're writing the previous block to that
slow floppy drive. Solution: Use a bigger input buffer.

Another scenario: You're running a BBS. The caller is reading a
long message and decides it's boring. The caller pushes the
cancel key (control-C, or S, or whatever you've decided makes
sense.) The BBS stops sending, but that big output buffer is
full of data -- and it just keeps coming, and coming ....
Solution: Use small i/o buffers for interacting with humans.

TOS provides an XBIOS function that returns a pointer to an
input buffer record. It requires one argument: The BIOS number
of a device (see above).

#define Iorec(dev) (void*) xbios(14, (short)(dev))

Assembly programmers: The xbios trap is #14, so push the device
number followed by two 14's, then do the trap. The pointer will
be found in d0.

The iorec structure is defined as follows:

struct iorec
        {
        char *buf;    /* pointer to an array: char buffer[bufsiz] */
        short bufsiz; /* size of the array         */
        short head;   /* index for writing         */
        short tail;   /* index for reading         */
        short low;    /* low water mark, for XON   */
        short high;   /* high water mark, for XOFF */
        }

For the AUX port, the output buffer record immediately follows
the input buffer record.

Before you go poking around inside these records, keep in mind
that they are internal to TOS, and they are shared by all
processes that run on your system. You can change the buffers
and even bypass GEMDOS and BIOS for reading and writing, if you
so desire, but you must be prepared to live with the
consequences.

In particular, be very careful to restore the original record
when your program terminates (unless you're writing a TSR with
the express purpose of enlarging a buffer).

Immediately after the serial port's input and output records are
some internal TOS variables that may be useful. The following
code has worked on all versions of TOS that I've encountered. I
don't know whether Atari guarantees that the variables won't be
changed on future versions of TOS.

With those cautions in mind, here is some code, originally
lifted from an early ST communications program written by Dale
Schumacher. I've been using the open_aux and close_aux functions
for years, but I haven't had a need to deal with the rsr, tsr,
and flow control variables.

typedef struct
        {
        struct iorec in;        /*  0 Input buffer record  */
        struct iorec out;       /* 14 Output buffer record */
        char rsr_status;        /* 28 MFP(0x1C) Receiver status */
        char tsr_status;        /* 29 MFP(0x1D) Transmit status */
        char xoff_sent;         /* 30 TRUE if we sent XOFF      */
        char xoff_received;     /* 31 TRUE if we got XOFF       */
        char mode;              /* 32 Bit 0 XON, Bit 1 RTS mode */
        char filler;            /* 33 Unused                    */
        } IOREC;

#define IBUFSIZ 1200
#define OBUFSIZ 2

char    st_ibuf[IBUFSIZ];       /* our own input buffer         */
char    st_obuf[OBUFSIZ];       /* and our own output buffer    */

IOREC   *st_sysr;               /* ptr to system rs232 record   */
IOREC   st_savr;                /* to save system rs232 record  */

IOREC   st_myiorec =
        {
        /* first, an input record */
        st_ibuf, IBUFSIZ, 0, 0, (IBUFSIZ/4), (3*(IBUFSIZ/4)),

        /* then an output record */
        st_obuf, OBUFSIZ, 0, 0, 0, 1,

        /* and the rsr, tsr, flow control stuff */
        0, 0, 0, 0, 0, 0
        };

void openaux()          /* set up our own rs232 input and output buffers */
        {
        while(Bconstat(AUX))            /* flush existing buffer */
                Bconin(AUX);
        st_sysr = (IOREC *)Iorec(0);
        st_savr = *st_sysr;             /* Save system buffer   */
        *st_sysr = st_myiorec;          /* Set my io buffer     */
        }

void closeaux()         /* restore system i/o buffer */
        {
        *st_sysr = st_savr;
        }


Take a break
------------

Some remote systems, notably Unix sites, need a BREAK signal to
wake them up and help them synchronize with a caller's bps rate.
To generate a BREAK on the ST, you briefly toggle the transmit
status register. Here is code that does so by directly accessing
the hardware on the ST. I think Dale Schumacher is the author of
this code.

#include <time.h>
#include <osbind.h>

void nap(n)                     /* do nothing for n-100ths of a second */
        register int n;
        {
        register clock_t t, dt;
        dt = (CLK_TCK * ((clock_t) n)) / ((clock_t) 100);
        t = clock() + dt;
        while(clock() < t)
                ;
        }

void snd_brk()          /* send a BREAK to the rs232 port */
        {
        register long ssp;
        register char *tsr;
        tsr = ((char *) 0xFFFFFA2DL);           /* address of tsr */
        ssp = (long) Super(0L);                 /* enter supervisor mode */
        *tsr = 0x09;                            /* turn break on */
        Super(ssp);                             /* return to user mode */
        nap(30);                                /* 0.3 second BREAK */
        ssp = (long) Super(0L);                 /* enter supervisor mode */
        *tsr = 0x01;                            /* turn break off */
        Super(ssp);                             /* return to user mode */
        }

The clock() function should be available with most C compilers.
If you're writing in a language that doesn't have an equivalent,
the trick is simply to peek at memory location 4BA (hex), which
is the 200Hz system timer. You must be in supervisor mode to do
so. The value is a 32-bit long.


File transfers
--------------

When transferring a binary file, the sender and the receiver
have to agree on a method for signalling the end of the
transmission. Ideally, they should have some method of
determining whether the file was mangled by line noise. And if
it was, there should be a way to retransmit the bad data.

The methods for solving these problems are called file-transfer
protocols, and their name should be legion, for they are many.
Xmodem, Ymodem, Zmodem, Quick B, WXmodem, Kermit, Punter, FAST,
Telink, UUCP 'G' ... the list goes on.

Most of them follow the same basic strategy. A file is broken up
into multiple packets. Each packet starts with a header that
includes a packet number and a mathematical value, called a
checksum, that can be used to determine whether the data in the
packet has been corrupted.

XMODEM is a very simple implementation of this strategy.

It uses 128-byte data packets. It uses packet headers consisting
of a a byte for the packet number, and (in the original version)
a byte for a checksum. The checksum is calculated by adding the
numeric value of each byte and throwing away the overflow. It's
not foolproof, but it detects most errors.

Later versions of Xmodem use a 16-bit ``cyclical redundancy
check'' calculated by using a fairly complex algorithm. It's
much harder to fool.

If the Xmodem receiver adds up all the values and finds they
don't match the total that was encoded in the packet header, it
sends a NAK (Negative AcKnowledgement) signal to the sender,
which then retransmits the corrupted packet. If they match, it
the receiver sends an ACK.

Because the packages are small, and because the sender always waits
for an ACK before proceeding, Xmodem is not particularly fast. When
XMODEM runs over a network that does its own error-checking and
packet operations internally (such as CompuServe or GEnie's data
systems), Xmodem can get painfully slow. That lonely ACK signal
can take a long time to wander through the network and get back
to you.

There are two ways of dealing with Xmodem's speed problems. One is to
use bigger packets, which is the approach taken by most alternatives,
including Ymodem. The other is to use ``windowing,'' which is used by
WXmodem, UUCP 'G' and Zmodem.

A windowing protocol doesn't wait for an ACK before sending.
Instead, it transmits the next block. If a NAK arrives, it can
back up and resend the bad block. The number of blocks that can
be ``remembered'' is the size of the ``window.'' A
sliding-window implementation is one in which the sender's
window moves along as you move through the file and get ACKs
from the receiver.

And now, the code.

This document isn't going to attempt to present a C
implementation of a file-transfer protocol. But the question of
``how do I calculate a checksum'' comes up frequently, here is C
code to create the values used by Xmodem, Ymodem and some
related programs.

Some CRC functions index the CRC values out of an array of
precalculated numbers. That approach probably is faster than the
one below, but the ST has plenty of horsepower for this sort of
thing.

Don't ask me to explain the math. I can't. I didn't write this;
I just use it.

 /*
  * This function calculates the CRC used by the XMODEM/CRC
  * Protocol.
  * The first argument is a pointer to the message block.
  * The second argument is the number of bytes in the message block.
  * The function returns a short integer that contains the CRC value.
  */
 short crc16(ptr, count)
 register char *ptr;
 register short count;
    {
    register short crc = 0, i;
    while(--count >= 0)
       {
       crc = crc ^ (short)*ptr++
  __________________________________________________________________________
 Back to the main menu
Last modified Nov 23, '94 by Christer Gustavsson
