Home  Articles  Programs 

RS232 Communication Library

Title:RS232 Communication Library
 Commlib2 is a RS232 communication package for the C64.
Filename:commlib2.bin (Click to download)
Language:Machine Language
Size:1266 bytes
Programmer:İlker Fıçıcılar


Commlib2 is a RS232 communication package for the C64.

CommLib2 consists of small routines to be used with standard User Port modem interfaces. In experiments, the NMI handler worked flawlessly at 19200bps in 2MHz mode of C128 (C64 mode, using VDC screen) and 9600bps in 1MHz C64 while VIC screen is closed. But there are some troubles with modems. These speeds are error-free with a null-modem cable to a PC, in contrary to modems. A modem (if its echo is on) passes the input to the output line. This means that sending and receiving occur at the same time, which lacks the NMI handler routine. Thus, the solution with modems is closing the echo before switching to a high speed. By the way, this package allows 4800bps with VIC open.

The definition of input buffer is flexible, this means you can use almost all of the internal memory as an input buffer. You should reserve large memory for the high baud rates, because the screen driver routine you have probably will not keep the data at high speeds. And, there is no output buffer implemented in this version. This is not a big problem for a monotasking application, also statistics says that the input/output transfer ratio is something 10/1 on the average for ordinary terminal usage.

As for the flow control methods, you can select one of these: rts/cts, xon/xoff, both or none. But, the application is responsible for this control, using 'enable' and 'disable' calls. The library does not check the buffer overflows. Nevertheless, you won't need to use them for flow control. In my tests, the routines kept the data flawless even at 19200bps. With the 'vdc80col' screen driver I have achieved 1730+ cps (same test for Desterm gave 760 cps at 9600bps).

Here is the usage explanation of the commlib2 RS232 library:

Memory Usage of This Version:

$ca00-$ca1f : jump table
$ca20-$ccff : the code
$cd00-$cd40 : settings and workspace
$9000-$a000 : input buffer
$ce00-$ceff : ascii-petscii table created by term routine

You can change the buffer and ASCII-table places easily if you want.

The Routines:

Setup      ($ca00) : Activates the NMI handler and sets the workspace
Uninstall  ($ca03) : Recovers the NMI handler, and deactivates the NMI
Enable     ($ca06) : Enables the incoming and outgoing data
Disable    ($ca09) : Suppresses the input (drops rts and/or sends xoff)
Sendit     ($ca0c) : Sending a byte through the rs232 channel
getbyt     ($ca0f) : Reads a byte from the rs232 receive buffer
putbyt     ($ca12) : Sending a byte safely (use it at 4800bps)
Asciitable ($ca15) : Prepares an ascii-petscii translation table
Term       ($ca18) : Simple terminal program (ctrl-j exits)
Speed      ($ca1b) : DTE baud (speed) selection

Location of the Routines' Code:

$ca20-$ca6e : setup
$ca76-$ca90 : uninstall
$ca91-$cab8 : disable
$cab9-$caee : enable
$caef-$cb21 : sendit
$cb23-$cb30 : NMI detect
$cb31-$cb64 : NMI sending part
$cb65-$cb6d : restore key
$cb6e-$cb83 : NMI startget
$cb84-$cbd0 : NMI receiving part
$cbd1-$cbe0 : safe send
$cbe1-$cbf8 : speed select
$cbf9-$cc3f : getbyt
$cc40-$cc9c : ascii-table
$cc9d-$ccff : terminal program

Default Values of Workspace:

:cd00 00 10 90 80 10 10 00 90
:cd08 00 90 00 80 00 80 00 00
:cd10 00 00 00 00 00 00 97 01
:cd18 a2 01 00 00 c3 c7 02 00

Explanation of Workspace Settings:

cd00 busy    : 1 byte  busy flag for sending process ($80=busy)
cd01 timeout : 1 byte  timeout in seconds (for putbyt (not implemented) )
cd02 inbuf   : 1 byte  pointer to input buffer start page
cd03 outbuf  : 1 byte  pointer to output buffer (not implemented)
cd04 inblen  : 1 byte  length of the input buffer in pages 
cd05 outblen : 1 byte  length of the output buffer in pages
cd06 inbsta  : 2 bytes start index of input buffer
cd08 inbend  : 2 bytes end index of input buffer
cd0a outbsta : 2 bytes start index of output buffer (not implemented)
cd0c outbend : 2 bytes end index of output buffer (not implemented)
cd0e inplock : 1 byte  lock status for input ($80=locked)
cd0f outlock : 1 byte  lock status for output
cd10 input   : 1 byte  byte read
cd11 output  : 1 byte  byte sent
cd12 tempinp : 1 byte  temporary for read byte
cd13 tempout : 1 byte  temporary for sent byte
cd14 inpidx  : 1 byte  bit counter while reading
cd15 outidx  : 1 byte  bit counter while sending
cd16 sbaud   : 2 bytes time period for data bits while sending
cd18 rbaud   : 2 bytes time period for data bits while receiving
cd1a flow    : 1 byte  flow control method ($80=rts/cts $40=xon/xoff)
cd1b $01     : 1 byte  ( value to write $dd00 )
cd1c $02     : 1 byte  must not be changed  ( mark bit )
cd1d $10     : 1 byte  must not be changed  ( space bit )
cd1e speed   : 1 byte  selected speed (default $02 = 2400bps)
cd1f         : 1 byte  reserved for escape interpretation
cd20         : 32 bytes timing values for various baud rates

Flow Control Selection:

method     $cd1a
--------   -----
none       $00
Xon/Xoff   $40
RTS/CTS    $80
both       $c0

Usages of the Routines:

* Setup ($ca00)

No preparation is required. Just call it to install the RS232 handler. Sets the necessary location in workspace (indexes and locks).


* Uninstall ($ca03)

A call to this routine deactivates the RS232 event handler.


* Enable ($ca06)

Sets NMI, sends xon and/or sets rts. So that getbyt and sendit routines work. Changes A register only.


* Disable ($ca09)

Waits the busy byte, sends xoff and/or drops rts, clears NMI. Use this, before accessing the serial ports (drive, printer, etc.). It changes only the A register.


* Sendit ($ca0c)

Waits the busy byte, and then sends the value in A register by 8N1 (8 data bits, no parity, 1 stop bit).

X and Y registers remain the same.

lda#$41 ; 'a' character
lda#$54 ; 't' character
lda#$0d ; carriage return
jsr$ca0c ; now you can check the input buffer for 'OK' of modem.

* Getbyt ($ca0f)

Gets one byte from the input buffer. If the buffer is empty sets the carry flag. X and Y registers remain unchanged upon return.

 bcs -

* sendsafe ($ca12)

Secure version of sendit, meaning that it avoids bad raster lines while sending a byte. To be used with 4800bps while VIC is open. It has the same usage with the sendit routine.

* Asciitable ($ca15)

Puts the ASCII-PETSCII conversion table to the memory page given by the A register. The table is simple and works only for tty emulation.

lda#$ce jsr$ca15

* Term ($ca18)

This is a little terminal program for testing the routines. It setups the handler and begins to scan keyboard and rs232. To exit from the term, either press ctrl-j or press the joystick button of port 2.

Upon exiting, it uninstalls the handler.


* Speed ($ca1b)

Selects the DTE baud rate. To use it put the speed index into A register and then call the routine:

lda#$02  ; 2400bps

Use this table of speed index:

A    :  00  :  01  :  02  :  03  :  04  :  05  :  06   :  07   :
Baud :  300 : 1200 : 2400 : 4800 : 7200 : 9600 : 14400 : 19200 :

Speed (baud) Selection Manually:

If you have troubles with the default values, you can fiddle with them. The following table shows the default values:

bps     $cd16 $cd17  $cd18 $cd19  table location
-----   ----- -----  ----- -----  --------------
300      $00   $0d    $10   $0d    $cd20
1200     $34   $03    $42   $03    $cd24
2400     $97   $01    $a2   $01    $cd28
4800***  $c7   $00    $d0   $00    $cd2c
7200**   $85   $00    $89   $00    $cd30
9600**   $60   $00    $65   $00    $cd34
14400*   $41   $00    $43   $00    $cd38
19200*   $2f   $00    $32   $00    $cd3c

* : at 2mhz mode of C128 only, I don't know if it works with SCPU.

** : While VIC is closed. It works flawlessly for me.

***: While VIC is open, use 'sendsafe' routine instead of 'sendit'.

+ : If you encounter a problem with modem, try turning the echo off: ATE0

++ : Don't forget to use 'sendsafe' at 4800 if VIC is open.

Also note that, at the speeds 4800bps and higher, $ffd2 routine lacks. Use another screen manager for full speed (such as 'vdc80col', 'flicktty' or D. Dallman's fast 80 column program).

By the way, keep the input buffer always big (default is 4096bytes) to keep up the data at higher rates.

Information on Used Hardware Registers:

$dd00-2 : out
$dd01-0 : in
$dd01-1 : rts
$dd01-6 : cts
$dd04,$dd05 : timer A (for sending)
$dd06,$dd07 : timer B (for receiving)
$dd0d : NMI
$dd0e : timer control A
$dd0f : timer control B

Manual to Use This Package in Basic:

. Setup Basic memory:


. Running simple terminal program:


pressing ctrl-j exits

. Setting up the RS232 routines:


. Closing the RS232 routines:


. Sending a byte:


. Sending a byte slowly and safely:


. reading a byte:


note that, (peek(783)and1) equals to 1 means that there is no data.

Example Terminal Program in Basic:

10 poke56,144:clr:sys51712
20 dim ge,se,a,c,a$,f$
30 ge=51727:se=51724:a=780:c=783:f$=chr$(133)
40 sysge:if(peek(c)and1)=0 then print chr$(peek(a));:goto40
50 geta$:ifa$<>""thenpokea,asc(a$):sysse
60 if a$<>f$ goto 40
70 sys51715:end

to quit from the program, press F1

to speed it up a little bit, change the line 40 like this:

40 sysge:if(peek(c)and1)=0then?chr$(peek(a));:sysge:?chr$(peek(a));:goto40

7200bps and 14400bps for IBM PC :

The two little .com programs below, switches the selected speed to either 7200bps or 14400bps.

For instance, first select 9600bps:

C:> mode 96,n,8,1,n

Then, switch to 14400bps:

C:> 14400

begin 644 7200.com

begin 644 14400.com ;NOL#[`R`[DI*L`#N2K`([KK[`^PD?^[-(`29 ` end

Some Benchmarks:

BAUD   Clock  Screen  cps
-----  -----  ------  ----
4800   1MHz    VDC    472
9600   1MHz    VDC    890
9600   2MHz    VDC    930
14400  2MHz    VDC    1375
19200  2MHz    VDC    1730

These are rough results. I timed the 10548 bytes (206 lines) file with a stop-watch.

NMI Handler (the heart of the library):

.,  cb22  78         sei 
.,  cb23  48         pha 
.,  cb24  ad 0d dd   lda $dd0d   ; read the NMI status
.,  cb27  10 3c      bpl $cb65   ; branch if it's Restore key
.,  cb29  29 03      and #$03    ; check if it's timer NMI
.,  cb2b  f0 41      beq $cb6e   ; if not it is the start bit
.,  cb2d  29 02      and #$02    ; check for timer B (receive)
.,  cb2f  d0 53      bne $cb84   ; branch to 'getbit' routine
.,  cb31  ad 1b cd   lda $cd1b   ; it was sending timer NMI
.,  cb34  8d 00 dd   sta $dd00   ; put the bit, which is in the queue
.,  cb37  ad 1d cd   lda $cd1d   ; load the space bit
.,  cb3a  4e 13 cd   lsr $cd13   ; get the next bit to send
.,  cb3d  b0 03      bcs $cb42   ; check whether it's 0 or 1
.,  cb3f  ad 1c cd   lda $cd1c   ; load the mark bit for 0
.,  cb42  8d 1b cd   sta $cd1b   ; put this value to queue byte
.,  cb45  ce 15 cd   dec $cd15   ; decrease the bit index
.,  cb48  30 0c      bmi $cb56   ; branch if the stop bit is sent
.,  cb4a  f0 02      beq $cb4e   ; branch for preparing the stop bit 
.,  cb4c  68         pla         ; return from the NMI
.,  cb4d  40         rti 
.,  cb4e  ad 1d cd   lda $cd1d   ; load the space bit
.,  cb51  8d 1b cd   sta $cd1b   ; put it to the queue
.,  cb54  68         pla 
.,  cb55  40         rti 
.,  cb56  a9 10      lda #$10    ; stop the timer-A
.,  cb58  8d 0e dd   sta $dd0e 
.,  cb5b  a9 01      lda #$01    ; stop the timer-A NMI
.,  cb5d  8d 0d dd   sta $dd0d
.,  cb60  8d 00 cd   sta $cd00   ; clear the busy bit
.,  cb63  68         pla 
.,  cb64  40         rti 
.,  cb65  8a         txa         ; this passes the NMI control to the 
.,  cb66  48         pha         ; original NMI handler.
.,  cb67  98         tya         ; since we detect the Restore key.
.,  cb68  48         pha 
.,  cb69  a0 00      ldy #$00
.,  cb6b  4c 56 fe   jmp $fe56   ; Jump to the Kernal NMI handler
.,  cb6e  a9 11      lda #$11    ; start the timer-B, we detected the RX
.,  cb70  8d 0f dd   sta $dd0f
.,  cb73  a9 10      lda #$10    ; stop the flag2 NMI
.,  cb75  8d 0d dd   sta $dd0d
.,  cb78  a9 82      lda #$82    ; start the timer-B NMI
.,  cb7a  8d 0d dd   sta $dd0d
.,  cb7d  a9 08      lda #$08    ; put the number of bits to be received,
.,  cb7f  8d 14 cd   sta $cd14   ; to the recieve index byte.
.,  cb82  68         pla 
.,  cb83  40         rti        
.,  cb84  ad 01 dd   lda $dd01   ; read the RX line (this is timer-B NMI)
.,  cb87  4a         lsr a       ; put into carry flag
.,  cb88  6e 12 cd   ror $cd12   ; put the carry flag into receive byte.
.,  cb8b  ce 14 cd   dec $cd14   ; decrese the bit number index
.,  cb8e  f0 02      beq $cb92   ; if it's last bit branch.
.,  cb90  68         pla 
.,  cb91  40         rti 
.,  cb92  a9 02      lda #$02    ; stop the timer-B
.,  cb94  8d 0d dd   sta $dd0d  
.,  cb97  a9 10      lda #$10    ; stop the timer-B NMI
.,  cb99  8d 0f dd   sta $dd0f
...                  ...         ; then place the read byte into the
...         ; receive buffer. 

Possible Optimizations:

. Use the NMI directly. That is close the ROMs ( #$35 -> $01 ), and direct the NMI to $cb22 ( #$22 -> $fffa , #$cb -> $fffb ). . If the distortion of the restore key is not important, remove the 'bpl' command at $cb27 and rearrange the handler. . You can use the zero-page variables. Try to move the workspace somewhere in the zero-page.

As a last note I can say that this library is written for the programmers, who want to write their own serial-communication oriented application.

Tags:C64, Networking

C64 Projects Twitter Page

İlker Fıçıcılar's CBM Page