[comp.lang.pascal] What am I doing wrong ?

abcscnuk@csuna.UUCP (Naoto Kimura) (07/09/88)

I seem to be having problems in detecting the configuration of the RS232
ports under Turbo pascal (version 3.01a).  I've looked at the BIOS
routines, and all I see are things to set the parameters, look at the
status, send and receive (polled) character, but no routines available
for checking the parameters.  I finally deceded to resort to looking at
the port registers, but am having difficulty in getting the correct
parameters.  I was wondering if anyone knew of a way to get the setup
parameters for the RS232 ports without having to resort to this.

The problem I am having is that when I use the routines (listings are
included in the article) that I have written, I get the wrong baud
rate (see code in AUXIO.INC).    I am hoping to get this code
working for 3.0, and then upgrading the stuff to 4.0 (the code has
some strangeness to make upgrading easier, also some strange stuff
appears because I want to be able to translate the code to C using
a translator).

The following are the files that I am using:

------ register.inc ------

(*====================================================================*\
|| MODULE NAME:  REGISTER.INC                                         ||
||                                                                    ||
|| DESCRIPTION:  This file declares the record type "registers" that  ||
||               is needed for using the BIOS and DOS interrupt       ||
||               services.                                            ||
||                                                                    ||
|| DEPENDENCIES: (none)                                               ||
||                                                                    ||
|| LAST MOD ON:  06/03/88                                             ||
\*====================================================================*)

type
    registers	= record
		case integer of
		1: (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags : integer);
		2: (AL,AH,BL,BH,CL,CH,DL,DH : byte)
		end;
------ machine.inc ------

(*====================================================================*\
|| MODULE NAME:  MACHINE.INC                                          ||
||                                                                    ||
|| DESCRIPTION:  This file contains definitions and declarations      ||
||               that are unique to the IBM-PC and Turbo Pascal.      ||
||                                                                    ||
|| DEPENDENCIES: (none)                                               ||
||                                                                    ||
|| LAST MOD ON:  06/06/88                                             ||
\*====================================================================*)

const
    FBLOCK_SIZE	= 128;		(* number of bytes in a block used   *
				 * by blockread and blockwrite       *)

    CONDENSED	= #15;		(*code to change to condensed print  *)
    NORMAL	= #18;		(*these codes are for Epson printer  *)

    EVEN_BITMASK= $fffe;	(* mask out lsb -- makes int even    *
				 * when converting to C, change this *
				 * to ~ 1  -- makes independent of   *
				 * word size, so it'll work better   *)
    LO15MASK	= $7fff;	(* lower 15 bits of integer          *)

type
    pointer	= ^byte;
    charset	= set of char;

const
    ALPHA_CHARS	: charset = [ 'A'..'Z','a'..'z' ];
    FNAME_CHARS	: charset = [ 'A'..'Z','a'..'z','0'..'9','\',':','.' ];
    WILD_CHARS	: charset = [ '*','?' ];
    REAL_CHARS	: charset = [ '0'..'9','+','-','.' ];
    INT_CHARS	: charset = [ '0'..'9','+','-' ];
    STR_CHARS	: charset = [ ' '..#255 ];
    YN_CHARS	: charset = [ 'Y','N','y','n' ];

(* handy-dandy routine from *Advanced Techniques in Turbo Pascal*     *)
(* by Charles C. Edwards                                              *)
procedure exchange (var S,D; L: integer);
    begin
	inline(	$1E		 {   PUSH DS       }
		/$C5/$B6/S	 {   LDS  SI,S[BP] }
		/$C4/$BE/D	 {   LES  DI,D[BP] }
		/$8B/$8E/L	 {   MOV  CX,[L+BP]}
		/$FC		 {   CLD           }
		/$26/$8A/$05	 {L: MOV AL,ES:[DI]}
		/$86/$04 	 {   EXCH [SI],AL  }
		/$46		 {   INC  SI       }
		/$AA		 {   STOSB         }
		/$E2/$F7 	 {   LOOP L        }
		/$1F);		 {   POP  DS       }
    end;    (* exchange *)
------ dos_vecs.inc ------

(*====================================================================*\
|| MODULE NAME:  DOS_VECS.INC                                         ||
||                                                                    ||
|| DESCRIPTION:  This library of routines is used to get and set DOS  ||
||               interrupt vectors.   Hopefully these are compatible  ||
||               the routines found in release 4.0 of Turbo Pascal.   ||
||                                                                    ||
|| DEPENDENCIES: REGISTER.INC, MACHINE.INC                            ||
||                                                                    ||
|| LAST MOD ON:  06/06/88                                             ||
\*====================================================================*)

procedure GetIntVec(	Interrupt_Number : byte;
		    var	Interrupt_Handler: pointer  );
    var
	regs	: registers;
    begin
	regs.AH := $35;
	regs.AL := Interrupt_Number;
	intr($21,regs);
	Interrupt_Handler := ptr(regs.ES,regs.BX)
    end;

procedure SetIntVec( Interrupt_Number  : byte;
		     Interrupt_Handler : pointer );
    var
	regs	: registers;
    begin
	regs.AH := $25;
	regs.AL := Interrupt_Number;
	regs.DS := seg(Interrupt_Handler^);
	regs.DX := ofs(Interrupt_Handler^);
	intr($21,regs)
    end;
------ data_seg.inc ------

const
    Interrupt_DS : integer = 0;
------ auxio.inc ------

(*====================================================================*\
|| MODULE NAME:  AUXIO.INC                                            ||
||                                                                    ||
|| DESCRIPTION:  This is a TURBO data-communications handler.  Much   ||
||               of this code was found in the book *Advanced         ||
||               Techniques in Turbo Pascal*  by Charles C. Edwards,  ||
||               but has bee modified somewhat to allow for future    ||
||               compatibility with Turbo 4.0 and eventual transla-   ||
||               tion of the programs to C.                           ||
||                                                                    ||
|| DEPENDENCIES: REGISTER.INC, MACHINE.INC, DOS_VECS.INC, DATA_SEG.INC||
||                                                                    ||
|| LAST MOD ON:  07/07/88                                             ||
\*====================================================================*)

const
    RS_BUFFER_SIZE = 8191;  { Size of bufer - 1 ; change if you want a
                              different buffer size }

    (*----------------------------------------------------------------*\
    | These values are used by the BIOS routines.                      |
    \*----------------------------------------------------------------*)
    _COM_BAUD		= $D0;	(* ###- ---- *)
    _COM_110		= $00;	(* 000- ---- *)
    _COM_150		= $20;	(* 001- ---- *)
    _COM_300		= $40;	(* 010- ---- *)
    _COM_600		= $60;	(* 011- ---- *)
    _COM_1200		= $80;	(* 100- ---- *)
    _COM_2400		= $A0;	(* 101- ---- *)
    _COM_4800		= $C0;	(* 110- ---- *)
    _COM_9600		= $D0;	(* 111- ---- *)

    _COM_PARITY		= $18;	(* ---# #--- *)
    _COM_NOPARITY	= $00;	(* ---0 0--- *)
    _COM_ODDPARITY	= $08;	(* ---0 1--- *)
    _COM_NVRMNDPARITY	= $10;	(* ---1 0--- *)
    _COM_EVENPARITY	= $18;	(* ---1 1--- *)

    _COM_STOPBITS	= $04;	(* ---- -#-- *)
    _COM_STOP1		= $00;	(* ---- -0-- *)
    _COM_STOP2		= $04;	(* ---- -1-- *)

    _COM_CHRBITS	= $03;	(* ---- --## *)
    _COM_CHR5		= $00;	(* ---- --00 *)
    _COM_CHR6		= $01;	(* ---- --01 *)
    _COM_CHR7		= $02;	(* ---- --10 *)
    _COM_CHR8		= $03;	(* ---- --11 *)

    _COM_NUMBAUD	= 8;	(* number of supported baud rates *)
    _COM_BAUD_TBL	: array [0..7] of byte = (
				_COM_110,  _COM_150,
				_COM_300,  _COM_600,
				_COM_1200, _COM_2400,
				_COM_4800, _COM_9600
			);
    _COM_BAUD_STR	: array [0..7] of array [1..4] of char = (
				' 110', ' 150', ' 300', ' 600',
				'1200', '2400', '4800', '9600'
			);
    _COM_NUMPARITY	= 4;	(* number of supported parity values *)
    _COM_PARITY_TBL	: array [0..3] of byte = (
				_COM_NOPARITY,     _COM_ODDPARITY,
				_COM_NVRMNDPARITY, _COM_EVENPARITY
			);
    _COM_PARITY_STR	: array [0..3] of array [1..10] of char = (
				 '      None', '       Odd',
				'Don''t care', '      Even'
			);
    _COM_NUMSTOP	= 2;	(* number of supported stop bits *)
    _COM_STOP_TBL	: array [0..1] of byte = (
				_COM_STOP1, _COM_STOP2
			);
    _COM_STOP_VAL	: array [0..1] of byte = (
				1, 2
			);
    _COM_NUMCHR		= 4;	(* number of supported chr bit values *)
    _COM_CHR_TBL	: array [0..3] of byte = (
				_COM_CHR5, _COM_CHR6,
				_COM_CHR7, _COM_CHR8
			);
    _COM_CHR_VAL	: array [0..3] of byte = (
				5, 6, 7, 8
			);

const
    (*----------------------------------------------------------------*\
    | These are masks to be used with the Line Control Register        |
    \*----------------------------------------------------------------*)
    WL_MASK	= $03;		(* word length select *)
    STB_MASK	= $04;		(* # Stop BiTs        *)
    PEN_MASK	= $08;		(* Parity ENable      *)
    EPS_MASK	= $10;		(* Even Parity Select *)
    STICKP_MASK	= $20;		(* Stick Parity       *)
    BRK_ON	= $40;		(* Set Break          *)
    DLAB_ON	= $80;		(* Divisor Latch      *)

    BRK_OFF	= $BF;		(* (~ BRK_ON)         *)
    DLAB_OFF	= $7F;		(* (~ DLAB_ON)        *)

    (*----------------------------------------------------------------*\
    | These masks are for use with the Interrupt Enable Register       |
    \*----------------------------------------------------------------*)
    EN_DATAAVAIL= $01;	(* Data Available             *)
    EN_THREMPTY	= $02;	(* Transmit Holding Reg EMpty *)
    EN_RCV	= $04;	(* Receive Line Status        *)
    EN_MODEM	= $08;	(* Modem Status               *)

type
    INS8250   = record
            THR: integer;	{Transmit Holding Register}
            RBR: integer;	{Recieve holding register }
            IER: integer;	{Interrupt Enable Register}
            LCR: integer;	{Line Control Register    }
            MCR: integer;	{Modem Control Register   }
            LSR: integer;	{Line Status Register     }
            MSR: integer;	{Modem Status Register    }
	    DLL: integer;	{Divisor Latch Lsb        }
	    DLM: integer	{Divisor Latch Msb        }
        end;
    RS_Baud	= (B110,B150,B300,B600,B1200,B2400,B4800,B9600);
    RS_Parity	= (P_None,P_Odd,P_Nevermind,P_Even);

const
    RS232 : array [1..2] of INS8250 = (
	( THR:$3F8; RBR:$3F8; IER:$3F9;
	  LCR:$3FB; MCR:$3FC; LSR:$3FD;
	  MSR:$3FE; DLL:$3F8; DLM:$3F9  ),
	( THR:$2F8; RBR:$2F8; IER:$2F9;
	  LCR:$2FB; MCR:$2FC; LSR:$2FD;
	  MSR:$2FE; DLL:$2F8; DLM:$2F9  ));
    Chk_DSR	: boolean = FALSE;
    Chk_CTS	: boolean = FALSE;

var
    RS_Error    : byte;
    RS_Buffer	: array [0..RS_BUFFER_SIZE] of byte;
    RS_Buf_Head,
    RS_Buf_Tail	: integer;
    RS_Com      : byte;
    OldRS_Vec1,
    OldRS_Vec2	: pointer;
    AuxIn,
    AuxOut      : integer;

(*--------------------------------------------------------------------*\
| NAME: RS232_Interrupt                                                |
|                                                                      |
|     This is the interrupt service routine written to handle the      |
| RS232 port.   This routine is triggered by data being available for  |
| reading.                                                             |
|                                                                      |
| EXTERNALS: const RS232, RS_BUFFER_SIZE                               |
|            var   Interrupt_DS, RS_Error, RS_Com, RS_Buffer,          |
|                  RS_Buf_Tail                                         |
\*--------------------------------------------------------------------*)
procedure RS232_Interrupt;
    begin
	inline ($50/			{PUSH AX}
		$53/			{PUSH BX}
		$51/			{PUSH CX}
		$52/			{PUSH DX}
		$57/			{PUSH DI}
		$56/			{PUSH SI}
		$06/			{PUSH ES}
		$1E/			{PUSH DS}
		$FB/			{STI}
		$2E/$A1/Interrupt_DS/	{MOV  AX,CS:Interrupt_DS}
		$8E/$D8 );		{MOV  DS,AX}
	RS_Error := port[RS232[RS_Com].LSR] and $1E;
	RS_Buffer[RS_Buf_Tail] := port[RS232[RS_Com].RBR];
	RS_Buf_Tail := (RS_Buf_Tail+1) mod (RS_BUFFER_SIZE+1);
	inline ($FA);			{CLI}
	port[$20] := $20;    {clr interrupt flag}
	inline ($1F/			{POP  DS}
		$07/			{POP  ES}
		$5E/			{POP  SI}
		$5F/			{POP  DI}
		$5A/			{POP  DX}
		$59/			{POP  CX}
		$5B/			{POP  BX}
  		$58/			{POP  AX}
		$8B/$E5/		{MOV  SP,BP}
		$5D/			{POP  BP}
		$CF)			{IRET}
    end; {RS232_Interrupt}

(*--------------------------------------------------------------------*\
| NAME: RS_Break                                                       |
|                                                                      |
|     This routine is used to generate a break signal on the RS232     |
| communications adapter.                                              |
|                                                                      |
| EXTERNALS: const RS232                                               |
|            var   RS_Com                                              |
\*--------------------------------------------------------------------*)
procedure RS_Break;
    begin
	port[RS232[RS_Com].LCR] := port[RS232[RS_Com].LCR] or BRK_ON;
	delay(200);   { 1/5 second }
	port[RS232[RS_Com].LCR] := port[RS232[RS_Com].LCR] and BRK_OFF
    end; {RS_Break}

(*--------------------------------------------------------------------*\
| NAME: RS232_Avail                                                    |
|                                                                      |
|     This routine is used to check if there is a character available  |
| in the input queue.                                                  |
|                                                                      |
| EXTERNALS: var RS_Buf_Head, RS_Buf_Tail                              |
\*--------------------------------------------------------------------*)
function RS232_Avail:boolean;
    begin
	RS232_Avail := RS_Buf_Head <> RS_Buf_Tail
    end;

(*--------------------------------------------------------------------*\
| NAME: RS232_In                                                       |
|                                                                      |
|     This routine is used by the Turbo text file driver to read a     |
| single character from the RS232 port.                                |
|                                                                      |
| EXTERNALS: const RS_BUFFER_SIZE                                      |
|            var   RS_Buf_Head, RS_Buf_Tail                            |
\*--------------------------------------------------------------------*)
function RS232_In: char;
    begin
	while RS_Buf_Head = RS_Buf_Tail do delay(10);
	RS232_In := char(RS_Buffer[RS_Buf_Head]);
	RS_Buf_Head := (RS_Buf_Head+1) mod (RS_BUFFER_SIZE+1)
    end;  {RS232_In}

(*--------------------------------------------------------------------*\
| NAME: RS232_Out                                                      |
|                                                                      |
|     This routine is used by the Turbo text file driver to output a   |
| character to the RS232 port.                                         |
|                                                                      |
| EXTERNALS: const RS232                                               |
|            var   RS_Com, Chk_DSR, Chk_CTS                            |
\*--------------------------------------------------------------------*)
procedure RS232_Out(Param:char);
    var
	i	: byte;
    begin
	while ((port[RS232[RS_Com].LSR] and $20) <> $20)
	    do delay(1);		(* xmit reg not empty  *)
	port[RS232[RS_Com].MCR]:=$0B;	(* Set request to send *)
	if Chk_DSR then
	    if Chk_CTS then
		while ((port[RS232[RS_Com].MSR] and $30) <> $30) do
		    delay(1)
	    else
		while ((port[RS232[RS_Com].MSR] and $20) <> $20) do
		    delay(1)
	else
	    if Chk_CTS then
		while ((port[RS232[RS_Com].MSR] and $10) <> $10) do
		    delay(1);
	port[RS232[RS_Com].THR]:=byte(Param);
	RS_Error := 0
    end;  {RS232_Out}

(*--------------------------------------------------------------------*\
| NAME: RS_parms                                                       |
|                                                                      |
|     This routine returns a byte showing the status of the RS232 port |
| in a byte with the same format as is passed to the BIOS RS232 setup  |
| routine.   The baud rate is determined by reading the Divisor latch  |
| and interpreting the contents (This table could be found in the      |
| IBM Technical Reference).                                            |
|                                                                      |
| EXTERNALS: const RS232, DLAB_ON, DLAB_OFF, _COM_110, _COM_150,       |
|                  _COM_300, _COM_600, _COM_1200, _COM_2400, _COM_4800,|
|                  WL_MASK, STB_MASK, PEN_MASK EPS_MASK                |
\*--------------------------------------------------------------------*)
function RS_parms( Com : byte ) : byte;
    var
	v	: integer;
	retval	: byte;
    begin
	(* get baud rate *)
	port[RS232[Com].LCR] := port[RS232[Com].LCR] or DLAB_ON;
	v := (port[RS232[Com].DLM] shl 8) or port[RS232[Com].DLL];
	port[RS232[Com].LCR] := port[RS232[Com].LCR] and DLAB_OFF;
	case v of	(* ">" mark those not supported by the BIOS *)
	$0900: retval :=         0;	(* >   50   *)
	$0600: retval :=         0;	(* >   75   *)
	$0417: retval :=  _COM_110;	(*    110   *)
	$0359: retval :=         0;	(* >  134.5 *)
	$0300: retval :=  _COM_150;	(*    150   *)
	$0180: retval :=  _COM_300;	(*    300   *)
	$00C0: retval :=  _COM_600;	(*    600   *)
	$0060: retval := _COM_1200;	(*   1200   *)
	$0040: retval :=         0;	(* > 1800   *)
	$003A: retval :=         0;	(* > 2000   *)
	$0030: retval := _COM_2400;	(*   2400   *)
	$0020: retval :=         0;	(* > 3600   *)
	$0018: retval := _COM_4800;	(*   4800   *)
	$0010: retval :=         0;	(* > 7200   *)
	$000C: retval := _COM_9600	(*   9600   *)
	else   retval := 0		(* >    ?   *)
	end;
	(* get word length, stop bits, and parity *)
	RS_parms := retval or (port[RS232[Com].LCR] 
			       and (WL_MASK or STB_MASK or
				    PEN_MASK or EPS_MASK   ));
    end;  {RS_parms}

(*--------------------------------------------------------------------*\
| NAME: RS_Initialize                                                  |
|                                                                      |
|     This routine sets up parameters for the RS232 port.   This is    |
| made to be compatible with the BIOS routine, for later translations  |
| to C.                                                                |
|                                                                      |
| EXTERNALS: const RS232                                               |
|            type  registers                                           |
|            var   RS_Buf_Head, RS_Buf_Tail, RS_Com                    |
\*--------------------------------------------------------------------*)
procedure RS_Initialize( Com, Parms	: byte);
    var
        Regs : registers;
    begin
	RS_Buf_Head := 0;
	RS_Buf_Tail := 0;
	RS_Com := Com;
	Regs.DX := Com-1;
	Regs.AX := Parms;
	intr($14,Regs);
	if RS_Com = 1 then
	    port[$21] := port[$21] and $EF
	else
	    port[$21] := port[$21] and $F7;
	port[RS232[RS_Com].LCR] := port[RS232[RS_com].LCR] and DLAB_OFF;
	port[RS232[RS_Com].IER] := EN_DATAAVAIL;
	port[RS232[RS_Com].MCR] := $08
    end;    (* RS_Initialize *)

type
    RS_Config = record
	    Com,
	    Speed,
	    Parity,
	    Stop,
	    Length	: byte
	end;

(*--------------------------------------------------------------------*\
| NAME: Get_RS_setup                                                   |
|                                                                      |
\*--------------------------------------------------------------------*)
procedure Get_RS_Setup(var Setup : RS_Config);
    var
	parms : byte;
    begin
	Setup.Com := RS_Com;
	parms := RS_parms(RS_Com);	(* get parameters *)
	Setup.Speed  := (parms and _COM_BAUD) shr 5;
	Setup.Parity := (parms and _COM_PARITY) shr 3;
	Setup.Stop   := (parms and _COM_STOPBITS) shr 2;
	Setup.Length := parms and _COM_CHRBITS
    end;    (* Get_RS_Setup *)

(*--------------------------------------------------------------------*\
| NAME: RS_Install                                                     |
|                                                                      |
|     This routine is used to disable the interrupts from the port and |
| to restore the old interrupt vectors.                                |
|                                                                      |
| EXTERNALS: var       OldRS_Vec1, OldRS_Vec2, AuxIn, AuxOut           |
|            procedure GetIntVec, SetIntVec, RS232_Interrupt, RS232_In |
|                      RS232_Out                                       |
\*--------------------------------------------------------------------*)
procedure RS_Install;
    begin
	GetIntVec($0C,OldRS_Vec1);
	GetIntVec($0B,OldRS_Vec2);
	port[$21] := port[$21] or $18;
	port[RS232[1].LCR] := port[RS232[1].LCR] and $7F;
	port[RS232[1].IER] := 0;
	port[RS232[1].MCR] := 0;
	port[RS232[2].LCR] := port[RS232[2].LCR] and $7F;
	port[RS232[2].IER] := 0;
	port[RS232[2].MCR] := 0;
	SetIntVec($0C,ptr(cseg,ofs(RS232_Interrupt)));
	SetIntVec($0B,ptr(cseg,ofs(RS232_Interrupt)));
	RS_Com   := 1;
	AuxIn    := AuxInPtr;
	AuxOut   := AuxOutPtr;
	AuxInPtr := ofs(RS232_In);
	AuxOutPtr:= ofs(RS232_Out)
    end;  {RS_Install}

(*--------------------------------------------------------------------*\
| NAME: RS_Cleanup                                                     |
|                                                                      |
|     This routine is used to disable the interrupts from the port and |
| to restore the old interrupt vectors.                                |
|                                                                      |
| EXTERNALS: const     RS232                                           |
|            var       RS_Com, OldRS_Vec1, OldRS_Vec2, AuxIn, AuxOut   |
|            procedure SetIntVec                                       |
\*--------------------------------------------------------------------*)
procedure RS_Cleanup;
    var
	Regs : registers;
    begin
	port[$21] := port[$21] or $18;
	port[RS232[RS_Com].LCR] := port[RS232[RS_Com].LCR] and $7F;
	port[RS232[RS_Com].IER] := 0;
	port[RS232[RS_Com].MCR] := 0;
	SetIntVec($0C,OldRS_Vec1);
	SetIntVec($0B,OldRS_Vec2);
	AuxInPtr := AuxIn;
	AuxOutPtr:= AuxOut
    end;  {RS_Cleanup}
------ end of listings ------

                //-n-\\				Naoto Kimura
        _____---=======---_____			(csun!csuna!abcscnuk)
    ====____\   /.. ..\   /____====
  //         ---\__O__/---         \\	Enterprise... Surrender or we'll
  \_\                             /_/	send back your *&^$% tribbles !!