[comp.sys.amiga.tech] Using the serial port h/w

snowdond@r2.cs.man.ac.uk (D.N.Snowdon (MSc PJ)) (12/03/90)

  Has anyone out there got any code (C or asm) which reads & writes 
to the serial port by acessing the hardware (via misc.resource) and
using the serial interupts?
  If you have (and you wouldn't mind sharing it) could you please
mail me a copy  - I've read the hardware manual but am not entirely
clear on how to go about things.
			thanks

snowdond@uk.ac.man.cs.r2

jcs@crash.cts.com (John Schultz) (12/05/90)

In <1953@m1.cs.man.ac.uk> snowdond@r2.cs.man.ac.uk (D.N.Snowdon (MSc PJ)) writes:


>  Has anyone out there got any code (C or asm) which reads & writes 
>to the serial port by acessing the hardware (via misc.resource) and
>using the serial interupts?
>  If you have (and you wouldn't mind sharing it) could you please
>mail me a copy  - I've read the hardware manual but am not entirely
>clear on how to go about things.
>			thanks

>snowdond@uk.ac.man.cs.r2


Here you go:

                Interrupt-driven Serial I/O Testbed
                          by John Schultz
                              4/16/90

  This code directly accesses the hardware and achieves very high
baud rates without errors.  This code should be legal if the serial
device is first opened and exclusive access is granted.  Speeds of
over 130k baud between a stock, nofastmem 500 and a 25mhz GVP 68030
have been achieved.  The '030 Amiga can go much faster; it can read
at least 223,720 baud.  The 500 poops out at 132,575 baud.  Writing
fast is no problem, reading eats up some bandwidth, and is thus
the bottleneck.  Polled I/O (yuk) can yield faster rates. These
baud rates were accomplished with a direct 2,3,7 wire, 50 foot
shielded cable.  Max baud rates may vary depending on cable and
hardware configurations.
  The current test code, ST (Serial Test), writes characters typed
on the keyboard 10 times.  Errors at high speed show up when less
than 10 characters are received or funny characters are inserted.
  I can run DNet and leave it running while I run this code!  DNet
must be idling, and the baud rate must be the same as DNet's (usually
19200) the last time you run ST.  This is very useful as quiting and
restarting DNet is not necessary.
  Part of the original assembly code came from the Amiga Transactor,
September 1989: Volume 2, Issue 6,pp 42-44, by Mike Schwartz.  The 
example changed the AutoVectors directly, and had a few typos. I use
SetIntVector(), and do things a little differently with the rbf and
tbe code. This was pretty much the first assembly language program I
wrote, so experts feel free to point out improvements. 
  I wrote the code using Lattice 5.04.  I currently use the Cape
or Devpac assemblers for speed, but for this example used Lattice's
asm (everyone with Lattice should have it).

  As always, please point out any optimizations, errors, or illegal
acts committed in this code. If you enhance the code, please email me
a copy.

  Use this code as you wish; no strings, ropes, cables, or
bungee cords attached.



    John

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	ser/input.c
#	ser/input.h
#	ser/inputhandler.a
#	ser/makefile
#	ser/readme
#	ser/serial.a
#	ser/st.c
# This archive created: Tue Dec  4 10:10:20 1990
export PATH; PATH=/bin:$PATH
if test -f 'ser/input.c'
then
	echo shar: will not over-write existing file "'ser/input.c'"
else
cat << \SHAR_EOF > 'ser/input.c'
/* input.c
   created by John Schultz, 10-Sep-89
   
*/

#include <exec/types.h>
#include <exec/io.h>  
#include <exec/ports.h>
#include <exec/interrupts.h>  
#include <proto/exec.h>
#include <proto/graphics.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include "input.h"

unsigned short volatile raw; /* raw key value */

struct MsgPort indevport;
struct MsgPort * indevportptr;
struct IOStdReq * inreqblockptr;
struct Interrupt inhandler;
struct InputEvent * eventp;

extern void totalinput();

int createti(void) {

  indevportptr = CreatePort("indevport",0);
  if (!indevportptr) {
    return 0;
  }

  inreqblockptr = CreateStdIO((struct MsgPort *)indevportptr);
  if (!inreqblockptr) {
    return 0;
  }

  inhandler.is_Data = (APTR)&raw;
  inhandler.is_Code = totalinput;
  inhandler.is_Node.ln_Pri = 51; /* One ahead of Intuition */

  if (OpenDevice("input.device",0L,
                 (struct IORequest *)inreqblockptr,0L)) {
    return 0; 
  }

  inreqblockptr->io_Command = IND_ADDHANDLER;
  inreqblockptr->io_Data    = (APTR)&inhandler;
  
  if (DoIO((struct IORequest *)inreqblockptr)) {
    return 0;
  }

  GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0);
  if(!GfxBase) return 0;

  FreeSprite(0);

  CloseLibrary((struct Library *)GfxBase);

  return 1;  

} /* end createti */

void deleteti(void) {

  if ((indevportptr) && (inreqblockptr)) {
    inreqblockptr->io_Command = IND_REMHANDLER;
    inreqblockptr->io_Data = (APTR)&inhandler;
    DoIO((struct IORequest *)inreqblockptr);
    CloseDevice((struct IORequest *)inreqblockptr);
    DeleteStdIO(inreqblockptr);
    DeletePort(indevportptr);
  } /* end if */

} /* END deleteti */


/* END input.c */
SHAR_EOF
fi # end of overwriting check
if test -f 'ser/input.h'
then
	echo shar: will not over-write existing file "'ser/input.h'"
else
cat << \SHAR_EOF > 'ser/input.h'
/* input.h - total input
   created by John Schultz, 10-Sep-89
*/

#ifndef INPUTTI
#define INPUTTI

extern unsigned short volatile raw;  /* raw key input */

extern int createti(void);

extern void deleteti(void);

#endif

/* end input.h */
SHAR_EOF
fi # end of overwriting check
if test -f 'ser/inputhandler.a'
then
	echo shar: will not over-write existing file "'ser/inputhandler.a'"
else
cat << \SHAR_EOF > 'ser/inputhandler.a'
; Input Handler
; created by John Schultz, 17-Sep-89

	section	"TotalInput",code

	xdef	_totalinput

; Input event structure offsets

IECLASS	equ	4
IECODE	equ	6

; A0 = inputevent pointer
; A1 = data pointer

_totalinput:
	cmp.b	#1,IECLASS(A0)	; event->ie_Class == IECLASS_RAWKEY ?
	bne.b	notrawkey
	move.w	IECODE(A0),(A1)	; raw = event->ie_Code
notrawkey:
	moveq.l	#0,D0		; don't pass events to intuition
	rts

	END

SHAR_EOF
fi # end of overwriting check
if test -f 'ser/makefile'
then
	echo shar: will not over-write existing file "'ser/makefile'"
else
cat << \SHAR_EOF > 'ser/makefile'
# serial makefile, created by John Schultz, 17-Sep-89

st: st.o serial.o input.o inputhandler.o
  blink from lib:c.o+st.o+serial.o+input.o+inputhandler.o to st \
             lib lib:lc.lib sc sd nd

st.o: st.c
  lc -O st.c

serial.o: serial.a
  asm serial.a

input.o: input.h input.c
  lc input.c

inputhandler.o: inputhandler.a
  asm inputhandler.a

SHAR_EOF
fi # end of overwriting check
if test -f 'ser/readme'
then
	echo shar: will not over-write existing file "'ser/readme'"
else
cat << \SHAR_EOF > 'ser/readme'

                Interrupt-driven Serial I/O Testbed
                          by John Schultz
                              4/16/90

  This code directly accesses the hardware and achieves very high
baud rates without errors.  This code should be legal if the serial
device is first opened and exclusive access is granted.  Speeds of
over 130k baud between a stock, nofastmem 500 and a 25mhz GVP 68030
have been achieved.  The '030 Amiga can go much faster; it can read
at least 223,720 baud.  The 500 poops out at 132,575 baud.  Writing
fast is no problem, reading eats up some bandwidth, and is thus
the bottleneck.  Polled I/O (yuk) can yield faster rates. These
baud rates were accomplished with a direct 2,3,7 wire, 50 foot
shielded cable.  Max baud rates may vary depending on cable and
hardware configurations.
  The current test code, ST (Serial Test), writes characters typed
on the keyboard 10 times.  Errors at high speed show up when less
than 10 characters are received or funny characters are inserted.
  I can run DNet and leave it running while I run this code!  DNet
must be idling, and the baud rate must be the same as DNet's (usually
19200) the last time you run ST.  This is very useful as quiting and
restarting DNet is not necessary.
  Part of the original assembly code came from the Amiga Transactor,
September 1989: Volume 2, Issue 6,pp 42-44, by Mike Schwartz.  The 
example changed the AutoVectors directly, and had a few typos. I use
SetIntVector(), and do things a little differently with the rbf and
tbe code. This was pretty much the first assembly language program I
wrote, so experts feel free to point out improvements. 
  I wrote the code using Lattice 5.04.  I currently use the Cape
or Devpac assemblers for speed, but for this example used Lattice's
asm (everyone with Lattice should have it).

  As always, please point out any optimizations, errors, or illegal
acts committed in this code. If you enhance the code, please email me
a copy.

  Use this code as you wish; no strings, ropes, cables, or
bungee cords attached.



    John
SHAR_EOF
fi # end of overwriting check
if test -f 'ser/serial.a'
then
	echo shar: will not over-write existing file "'ser/serial.a'"
else
cat << \SHAR_EOF > 'ser/serial.a'
; Serial.a
; created by John Schultz, 17-Sep-89
; last modified 17-April-90

	section	Serial,CODE

	xref	_raw

	xdef	_baudper
	xdef	_openserial
	xdef	_closeserial
	xdef	_sendser
	xdef	_readser
	xdef	_checkser
	xdef	_rawtoascii
	xdef	_sendsernobuff


_LVOSetIntVector equ	$FFFFFF5E

ESCAPE		equ	69		; input-handler rawkey code

CLOCK		equ	3579545
_baudper	dc.w 	(CLOCK/19200)-1	; defaualt

CUSTOM		equ	$dff000
SERDATR		equ	$000018
SERDAT		equ	$000030
SERPER		equ	$000032

INTENA		equ	$00009a
INTENAR		equ	$00001c
INTREQ		equ	$00009c

TBEQSIZE	equ	128		; buffer size in bytes,
RBFQSIZE	equ	128		; increase or decrease as needed

LED		equ	$bfe001		; toggle power led, audio filter

ledroll		dc.b	%00010001	; flash every 4 bytes
ledpad		dc.b	0

oldintena	dc.w	0

tbequeue	ds.b	TBEQSIZE	; circular queue
tbeqend
tbehead		dc.l	tbequeue
tbetail   	dc.l	tbequeue

oldtbeinterrupt	dc.l	0
oldrbfinterrupt	dc.l	0

tbeinterrupt
	dc.l	0	; ln_succ
	dc.l	0	; ln_prec
	dc.b	2	; NT_INTERRUPT
	dc.b	127	; ln_pri
	dc.l	0	; ln_name
	dc.l	tbehead	; is_data
	dc.l	tbehandler ; is_code

rbfinterrupt
	dc.l	0	; ln_succ
	dc.l	0	; ln_prec
	dc.b	2	; NT_INTERRUPT
	dc.b	127	; ln_pri
	dc.l	0	; ln_name
	dc.l	rbftail	; is_data
	dc.l	rbfhandler ; is_code


tbehandler	
	move.w	#1,INTREQ(a0)		; clear TBE interrupt
	move.l	(a1),a1			; a1 = tbehead, (a1) = pointer
	cmp.l	tbetail,a1		; now a1 = pointer
	beq.b	99$
	move.w	SERDATR(a0),d0		; check to make sure emtpy
	btst	#13,d0
	beq.b	99$
	move.w	#$100,d0
	move.b	(a1)+,d0		; get byte from queue
	move.w	d0,SERDAT(a0)		; send byte
	cmpa.l	#tbeqend,a1
	bne.b	10$
	lea	tbequeue(pc),a1
10$	move.l	a1,tbehead
99$
	rts

rbfqueue	ds.b	RBFQSIZE
rbfqend	
rbfhead		dc.l	rbfqueue
rbftail		dc.l	rbfqueue

rbfhandler
	move.w	SERDATR(a0),d0		; a0 = custom
	move.w	#$800,INTREQ(a0)	; clear RBF interrupt
	move.l	(a1),a0			; a1 = rbftail, get pointer
	move.b	d0,(a0)+		; write byte to buffer
	cmpa.l	#rbfqend,a0
	bne.b	10$
	lea	rbfqueue(pc),a0
10$	move.l	a0,(a1)			; a1 = rbftail

;99$
;	move.b	ledroll(pc),d0		; remove from 99$-100$ for more
;	rol.b	#1,d0			; speed
;	move.b	d0,ledroll
;	btst	#1,d0
;	beq.b	100$
;	bchg.b	#1,LED			; toggle power led / audio filter
;100$
	rts

_openserial
	move.l	a6,-(sp)
	lea	CUSTOM,a0
	move.w	#$4000,INTENA(a0)	; disable
	move.l	$4,a6			; execbase
	lea	rbfinterrupt(pc),a1
	moveq.l	#11,d0
	jsr	_LVOSetIntVector(a6)
	move.l	d0,oldrbfinterrupt
	lea	tbeinterrupt(pc),a1
	moveq.l	#0,d0
	jsr	_LVOSetIntVector(a6)
	move.l	d0,oldtbeinterrupt

	lea	CUSTOM,a0
	move.w	_baudper,SERPER(a0)	; set period

	move.w	#$3fff,$bfd0fe		; init handshake lines
	move.w	INTENAR(a0),oldintena
	move.w	#$8801,INTENA(a0)	; turn on RBF and TBE INTENA
	move.w	#$0801,INTREQ(a0)	; clear RBF and TBE INTREQ
	move.w	#$c000,INTENA(a0)	; enable
	move.l	(sp)+,a6
	rts


_closeserial
	move.l	a6,-(sp)
	lea	CUSTOM,a0
	move.w	#$4000,INTENA(a0)	; disable
	move.l	$4,a6			; execbase
	move.l	oldrbfinterrupt,a1
	moveq.l	#11,d0
	jsr	_LVOSetIntVector(a6)
	move.l	oldtbeinterrupt,a1
	moveq.l	#0,d0
	jsr	_LVOSetIntVector(a6)

	lea	CUSTOM,a0
	move.w	#$0801,INTENA(a0)	; turn off RBF and TBE INTENA
	move.w	oldintena,d0
	and.w	#$0801,d0		; mask for bit 11 and bit 1.
	bset	#15,d0			; set/clr = 1 (set)
	move.w	d0,INTENA(a0) 		; reset old RBF and TBE INTENA

	bclr.b	#1,LED

	move.w	#$0801,INTREQ(a0)	; clear RBF and TBE INTREQ
	move.w	#$c000,INTENA(a0)	; enable
	move.l	(sp)+,a6
	rts

_sendser
	lea	CUSTOM,a0
	move.w	#$4000,INTENA(a0)

	move.l	tbetail,a1
	cmpa.l	tbehead,a1
	beq.b	5$
	move.w	#$8001,INTREQ(a0)	; generate a tbe interrupt
5$
	move.b	d0,(a1)+		; queue up byte
	cmpa.l	#tbeqend,a1
	bne.b	10$
	lea	tbequeue(pc),a1
10$	move.l	a1,tbetail
	
	move.w	#$c000,INTENA(a0)
	rts


; send byte in d0 to serial port, non-buffered.
_sendsernobuff
	lea	CUSTOM,a0
	move.w	#$4000,INTENA(a0)
1$	move.w	SERDATR,d1
	btst	#13,d1			; check TBE
	beq.b	1$			; buffer busy
	
	move.w	#$100,d1
	move.b	d0,d1
	move.w	d1,SERDAT(a0)
2$	move.w	SERDATR(a0),d1
	btst	#13,d1			; check TBE
	beq.b	2$			; buffer busy
	move.w	#$001,INTREQ(a0)	; clear TBE interrupt

	move.w	#$c000,INTENA(a0)
	rts


; uses a0
_readser
	cmp.w	#ESCAPE,_raw	; quits on ESC from input handler
	beq.b	20$
	move.l	rbfhead,a0
	cmpa.l	rbftail,a0
	beq.b	_readser
	move.b	(a0)+,d0
	cmpa.l	#rbfqend,a0
	bne.b	10$
	lea	rbfqueue(pc),a0
10$	move.l	a0,rbfhead
20$
	rts

_checkser
	move.l	rbfhead,a0
	cmpa.l	rbftail,a0
	bne.b	dataisin
	moveq.l	#0,d0
	rts
dataisin
	moveq.l	#1,d0
	rts


; rawkey in d0
; ascii returned in d0
_rawtoascii
	cmp.b	#79,d0
	bls.b	getascii	
	moveq.l	#0,d0
	rts
getascii
	lea	asciitable,a0
	move.b	0(a0,d0),d0
	rts


	section	asciitable,data

; This is a quick trick, don't do this for anything other than
; home use, boys and girls.  Use rawkeyconvert, etc.

asciitable
	dc.b	'`'	; backquote `
	dc.b	'1'
	dc.b	'2'
	dc.b	'3'
	dc.b	'4'
	dc.b	'5'
	dc.b	'6'
	dc.b	'7'
	dc.b	'8'
	dc.b	'9'
	dc.b	'0'
	dc.b	'-'
	dc.b	'='
	dc.b	$0D	; \
	dc.b	0
	dc.b	'0'	; keypad 0
	dc.b	'q'
	dc.b	'w'
	dc.b	'e'
	dc.b	'r'
	dc.b	't'
	dc.b	'y'
	dc.b	'u'
	dc.b	'i'
	dc.b	'o'
	dc.b	'p'
	dc.b	'['
	dc.b	']'
	dc.b	0
	dc.b	'1'
	dc.b	'2'
	dc.b	'3'
	dc.b	'a'
	dc.b	's'
	dc.b	'd'
	dc.b	'f'
	dc.b	'g'
	dc.b	'h'
	dc.b	'j'
	dc.b	'k'
	dc.b	'l'
	dc.b	';'
	dc.b	$27	; ' single quote
	dc.b	0
	dc.b	0
	dc.b	'4'
	dc.b	'5'
	dc.b	'6'
	dc.b	0
	dc.b	'z'
	dc.b	'x'
	dc.b	'c'
	dc.b	'v'
	dc.b	'b'
	dc.b	'n'
	dc.b	'm'
	dc.b	','
	dc.b	'.'
	dc.b	'/'
	dc.b	0
	dc.b	'.'	; keypad
	dc.b	'7'
	dc.b	'8'
	dc.b	'9'
	dc.b	' '
	dc.b	$8	; backspace
	dc.b	$9	; tab
	dc.b	$D	; enter  cr
	dc.b	$D	; return cr
	dc.b	$1B	; ESC
	dc.b	$7f	; delete
	dc.b	''
	dc.b	0
	dc.b	0
	dc.b	'-'	; keypad
	dc.b	0
	dc.b	0	
	dc.b	$b	; up arrow
	dc.b	$a	; down arrow
	dc.b	$20	; rt arrow
	dc.b	$8	; left arrow
	
	END
SHAR_EOF
fi # end of overwriting check
if test -f 'ser/st.c'
then
	echo shar: will not over-write existing file "'ser/st.c'"
else
cat << \SHAR_EOF > 'ser/st.c'
/* ST.c : Serial Test */
/* Created by John Schultz, 4/16/90 */
/* Interrupt driven serial I/O testbed */

#include <stdio.h>
#include <dos.h>
#include "input.h"

#define CLOCK 3579545

#define MINBAUD 300        /* Change these at will */
#define MAXBAUD 230000

#define BUFFSIZE 80

#define ESCAPE 69

extern void openserial(void);
extern void __asm sendser(register __d0 char c);
extern void __asm sendsernobuff(register __d0 char c);
extern char __asm readser(void);
extern short __asm checkser(void);
extern char __asm rawtoascii(register __d0 char c);

extern short far baudper;

void main(int argc,char ** argv) {
short i = 0;
long console;
long baud;
char traw;
short notquit = 1;
char buffer[BUFFSIZE];


  if (argc != 2) {
    printf("USAGE: st <baud>\n");
    exit(0);
  }

  baud = atoi(argv[1]);
  if (baud < MINBAUD)
    baud = MINBAUD;
  else if (baud > MAXBAUD)
    baud = MAXBAUD;

  printf("Setting baud to %d. Press ESC to quit.\n",baud);
  
  baudper = (CLOCK / baud)-1;

  console = _dopen("*",MODE_OLDFILE);
  if (console == -1) {
    printf("Couldn't open console.\n");
    exit(0);
  }

  if (createti()) {
    openserial();
    traw = 0;
    raw = 0;

    while (notquit) {
      if (raw) {
        traw = raw;
        raw = 0;
	if (traw == ESCAPE) notquit = 0;    /* ESC */
        traw = rawtoascii(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);
        sendser(traw);

      }

      if (checkser())
        for (i = 0;
            (i < BUFFSIZE) && (checkser());
            buffer[i++] = readser());
      
      if (i > 0) _dwrite(console,buffer,i);

      i = 0;

    } /* while notquit */

    closeserial();
    deleteti();
  } /* if createti */

  printf("\n");

} /* END ST.c */
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0