From:	IN%"MERTUS@BROWNCOG.BITNET" 17-APR-1992 04:48
To:	IN%"kgreen@CCIT.ARIZONA.EDU"
CC:	
Subj:	DT2801

Return-path: <MERTUS@BROWNCOG.BITNET>
Received: from Arizona.edu (Osprey.Telcom.Arizona.EDU) by CCIT.ARIZONA.EDU
 (PMDF #12663) id <01GIXWMA55EO90N039@CCIT.ARIZONA.EDU>; Fri,
 17 Apr 1992 04:48 MST
Received: from BROWNCOG.BITNET (MERTUS@BROWNCOG) by Arizona.edu (PMDF #12663)
 id <01GIXWL4VMWGA9Q6MD@Arizona.edu>; Fri, 17 Apr 1992 04:47 MST
Received: from BROWNCOG.BITNET by BROWNCOG.BITNET (PMDF #12572) id
 <01GIY2TRG9SG0008YO@BROWNCOG.BITNET>; Fri, 17 Apr 1992 07:46 EST
Date: Fri, 17 Apr 1992 07:46 EST
From: MERTUS@BROWNCOG.BITNET
Subject: DT2801
To: kgreen@CCIT.ARIZONA.EDU
Message-id: <01GIY2TRG9SG0008YO@BROWNCOG.BITNET>
X-Envelope-to: kgreen
X-VMS-To: IN%"kgreen@rvax.ccit.arizona.edu"

How to assemble
 
tasm/mx dt2801
tlink/t/m dt2801,dt2801.drv,dt2801,
 
--------------------------------------------------------------------
;
;  File Name:  DT2801.ASM  Version 2.02
;  Created:     2-JAN-1989  2:54 on Compaq III by John Mertus
;  Revision #2: May 17 1991, 05:16 on the PS/2 60 by John Mertus
;
;*********************************************************************
;
;        This is the driver program for the DT2801 board.  It only
;  supports DMA output and input.
;
;  NOTE:  This software is provided for noncommerical uses only.
;  Any commerical use without my written permission is and will
;  be considered a violation of copyright laws.
;
;                                    -John Mertus  Jan 89
;
;  Edit history.  V1.01 inital release for D/A
;                 V2.00 Added A/D functions, global variables
;                       redefined set clocks, added set_channel
;                 V2.01 Fixed diachotic output. Added error report
;                       for too fast clock.
;                 V2.02 DA_Set_Channel now actually sets the channel
;
;
;*********************************************************************
;
        TITLE  DT2801 LIBRARY
;
        .MODEL COMPACT, C
;
; Equates and macros
;
   DT_RESET_CMD    equ 00H      ; Reset
   DT_CLEAR_CMD    equ 01H      ; Clear errors
   DT_RDERROR_CMD  equ 02H      ; Read errors
   DT_CLOCK_CMD    equ 03H      ; Set clock
   DT_TEST_CMD     equ 0BH      ; Test the board
   DT_STOP_CMD     equ 0FH      ; Stop conversion
 
;  Digital I/O commands
 
   Set_D_Input  equ  04H   ; Digital input
   Set_D_Output equ  05H   ; Digital output
   Read_DIO     equ  06H   ; Read digital input
   Write_DIO    equ  07H   ; Write digital output
 
;  D to A commands
 
   Write_D2A_I  equ  08H   ; Write D/A immediate
   Set_D2A      equ  09H   ; Set D/A parameters
   Start_D2A    equ  0AH   ; Write D/A
 
;  A to D commands
 
   READ_A2D_I   equ 0CH    ; Read A/D immediate
   Set_A2D      equ 0DH    ; Set A/D parameters
   Start_A2D    equ 0EH    ; Read A2D
 
;  Modifiers
 
   Ext_Trig     equ 080H    ; External trigger
   Int_Trig     equ 000H    ; Internal trigger
   Ext_Clock    equ 040H    ; External clock
   Cont_Mode    equ 020H    ; Continious mode
   DMA_Mode     equ 010H    ; DMA mode
 
;  Status register equates
 
   Comp_Err     equ 080H    ; Composite error
   Cmd_Stat     equ 08H     ; Command status
   Ready_Stat   equ 04H     ; Ready status
   DI_Stat      equ 02H     ; Data in status
   DO_Stat      equ 01H     ; Data out ready
 
;  DMA equates, same for both channels
 
   DMA_Chan_Reg  equ 0AH
   DMA_Mode_Reg  equ 0BH
   DMA_Data_Reg  equ 0CH
   DMA_Continuous equ 010H
   DMA_Base      equ 041H
   DMA_Read      equ 08H
   DMA_Write     equ 04H
 
 
; Start Mode equates
 
Mode_DMA_Cont equ 030H          ; DMA and continious
TIME_DELAY      equ 0FFFFH      ; Time after which board reports a time out
 
;
;----------------------------------------------------------------------
;
;   Offset for the C subroutines
;
        DAD_INIT        EQU     0
        DAD_RESET       EQU     1
        DAD_ERROR       EQU     2
        DA_SETC         EQU     3
        DA_STOP         EQU     4
        DA_CLOCK        EQU     5
        DA_STATUS       EQU     6
        DA_WOB          EQU     7
        DA_DMA          EQU     8
        DA_START        EQU     9
        AD_STOP         EQU     10
        AD_CLOCK        EQU     11
        AD_STATUS       EQU     12
        AD_WOB          EQU     13
        AD_DMA          EQU     14
        AD_START        EQU     15
        AD_SETC         EQU     16
;
;   Data segment.  This contains all the pointers to the subroutines
;  and all the global variables.
;
        .DATA
;
; start of local data, filled by the subroutines
;
;  Code starts here
;
        .CODE
        ASSUME CS:_TEXT, DS:NOTHING, ES:NOTHING, SS: NOTHING
 
        ORG 100h
;
;  Get the routine offsets.  This is fixed as follows:
;       First word, offset of GLOBAL data
;       Next  word, number of routines defined
;          Pairs (num,routine)
;
;       Each pair has two words, the first word is the routine number
;       as expected and defined by DTLIBDEF.I, and the second is
;       the offset of the routine.
;
DUMMY:  DW ID_BYTE                      ; Global data start
        DW 16                           ; Number of routines
        DW dt_reset,DAD_RESET
        DW DAD_error_msg,DAD_ERROR
 
        DW dt_clear,DA_STOP
        DW DAD_bf_status,DA_STATUS
        DW DA_set_channels,DA_SETC
        DW DA_set_clock,DA_CLOCK
        DW wait_on_buffer,DA_WOB
        DW DA_setup_dma,DA_DMA
        DW start_output,DA_START
 
        DW dt_clear,AD_STOP
        DW DAD_bf_status,AD_STATUS
        DW AD_set_clock,AD_CLOCK
        DW wait_on_buffer,AD_WOB
        DW AD_set_channels,AD_SETC
        DW AD_setup_dma,AD_DMA
        DW start_input,AD_START
;
;------------------------------------------------------------------------
; start of global data set via the CONFIG program
;
;  These variables must be loaded from the constants to define the DMA
;  channel used, it is the responsiblity of DTCONFIG to initialize them
;  properly.
;
ID_BYTE         DB      'DT2801   ',0
VERSION         DB      2,2             ; Version number High, Low
CENTER          DW      2048            ; A/D voltage zero
                DW      0,0,0
;
;  Start of local global data
;
DT_DATA         DW      2ECh            ; Data in/out
DT_CMD          DW      2EDh            ; Base_Add + 1  Command register
DT_STATUS       DW      2EDh            ; Base_Add + 1  Status reg
DMA_Channel     DW        1             ; DMA_Channel
DMA_Base_Reg    DW      06H
DMA_Count_Reg   DW      07H
DMA_Page_Reg    DW     082H
DMA_AD_Yes      DB      DMA_Base + DMA_Write + 1 - 1 + DMA_Continuous
DMA_DA_Yes      DB      DMA_Base + DMA_Read + 1 - 1 + DMA_Continuous
CLOCK_RATE      DD      800000;
;
;-------------------------------------------------------------------------
;  Local PID storage
;
        ERROR_1   EQU   1H              ; Illegal channel mode
        ERR_CLOCK EQU  40H              ; Clock error
        ERROR_100 EQU 100H              ; Channel list error
        ERROR_200 EQU 200H              ; Gain error
 
ERR_TABLE       DW ERR_1, ERR_2, ERR_4, ERR_8, ERR_10, ERR_20
                DW ERR_40, ERR_80, ERR_100, ERR_200, ERR_400
                DW ERR_800, ERR_1000, ERR_2000, ERR_4000, ERR_8000
;
;  Error report will create this error string
;
ERROR_STRING    DB '?DT2801-Error'
NULL            DB 0
;
;  error messages
;
ERR_1   DB '-Illegal mode',0
ERR_2   DB '-Command Overwrite',0
ERR_4   DB '-Clock error',0
ERR_8   DB '-Digital Port Select',0
ERR_10  DB '-Digital Port Set',0
ERR_20  DB '-D/A Channel Error',0
ERR_40  DB '-D/A Clock Error',0
ERR_80  DB '-D/A Conversion Error',0
ERR_100 DB '-A/D Channel Error',0
ERR_200 DB '-A/D Gain Error',0
ERR_400 DB '-A/D Clock Error',0
ERR_800 DB '-A/D Muliplexer Error',0
ERR_1000 DB '-A/D Conversion Error',0
ERR_2000 DB '-Data when command expected',0
ERR_4000 DB '-Data in ready will not clear',0
ERR_8000 DB '-Ready in will not set',0
;
;  This is the size of buffer in bytes
;
BOUNDARY        DW      4096
;
; Channel information for A/D
;
CLOCKF  DB      1                       ; Multiplication clock factor
GAIN    DB      0                       ; Global gain factor
SCHAN   DB      0                       ; Start channel
ECHAN   DB      0                       ; End channel
;
;  Start of code
;
        LOCALS $$
;
;--------------dt_stop-----------------------John Mertus---Jan 88---
;
;  This stops the DT board, notice no input is checked.
;       DS must point to the correct segment.
;
 
dt_stop PROC
 
        push    cx                      ; Save cx
        mov     dx,cs:[DT_CMD]          ; output the stop
        mov     al,DT_STOP_CMD
        out     dx,al
 
        mov     cx,100                  ; Do 100 loops
$$1:    loop    $$1
        mov     dx,cs:[DT_DATA]         ; read an input byte
        in      al,dx
 
        mov     cx,100                  ; Do 100 loops
$$2:    loop    $$2
 
        pop     cx                      ; Restore cx
        ret
 
dt_stop ENDP
 
;
;-----------put_cmd--------------------------John Mertus  Augest 85----
;
;   This outputs the command byte in al to the board
;
;       AX, CX, DX are destroyed.
;
put_cmd PROC
;
; Wait until board is ready to accept next command
; If ready doesn't set after a finite amount of time then
; return an error and set carry.
;
        mov     ah,al
        mov     dx,cs:[DT_STATUS]       ; output the stop
        mov     cx,TIME_DELAY           ; maximum loop
$$1:    in      al,dx                   ; get it
        and     al,Ready_Stat           ; ready bit set?
        jnz     $$2                     ; yes
        loop    $$1                     ; try again
        mov     ax,08000h               ; error
        stc                             ; set carry flag
        ret                             ; exit
 
$$2:    mov     al,ah
        mov     dx,cs:[DT_CMD]          ; output the stop
        out     dx,al
        xor     ax,ax                   ; no error
        clc                             ; clear carry flag
        ret
 
put_cmd ENDP
 
;
;-----------get_byte-------------------------John Mertus  January 89----
;
;   This gets a byte from the data register.
;
;       AL, CX, DX are destroyed.
;       AL returns with the byte and carry clear
;   on error carry is set and AX contains the error
;
get_byte  PROC
;
; Wait until board is ready with a byte
;
        mov     dx,cs:[DT_STATUS]       ; status command
        mov     cx,TIME_DELAY           ; delay
$$1:    in      al,dx                   ; get it
        and     al,Ready_Stat+DO_Stat   ; ready or Data out bit set?
        jnz     $$2                     ; yes
        loop    $$1
 
        mov     ax,8000h                ; report error
        stc                             ; set carry flag
        ret
 
$$2:    mov     dx,cs:[DT_DATA]         ; output the stop
        in      al,dx
        clc                             ; clear carry
        ret
 
get_byte ENDP
 
;
;-----------get_word-------------------------John Mertus  January 89----
;
;   This gets a word from the data register.
;
;       AX, DX are destroyed.
;       AX returns with the word.
;
get_word  PROC
 
        call    get_byte
        jc      $$1                     ; error
        mov     ah,al
        call    get_byte
        jc      $$1                     ; error
        xchg    ah,al
 
$$1:    ret
 
get_word  ENDP
 
;
;-----------put_byte-------------------------John Mertus  January 89----
;
;   This puts a byte to the data register.
;
;       AX, CX, DX are destroyed.
;       AL is the byte to put
;
put_byte  PROC
;
; Wait until board is ready with byte
;
        mov     ah,al
        mov     dx,cs:[DT_STATUS]       ; status command
        mov     cx,TIME_DELAY           ; delay
$$1:    in      al,dx                   ; get it
        and     al,DI_Stat              ; data in clear?
        jz      $$2                     ; yes
        loop    $$1
;
;  report error
;
        mov     ax,4000h                ; Data out error
        stc                             ; set carry bit
        ret
;
; no error
;
$$2:    mov     dx,cs:[DT_DATA]         ; output the stop
        mov     al,ah
        out     dx,al
        xor     ax,ax
        clc                             ; clear carry bit
        ret
 
put_byte  ENDP
 
;
;-----------put_word-------------------------John Mertus  January 89----
;
;   This puts a word to the data register.
;   DS must point to correct segment.
;
;       AX, DX are destroyed.
;       AX contains the word to send
;
put_word  PROC
        push    bx
        mov     bx,ax           ; save byte
 
        call    put_byte        ; put low byte
        jc      $$1             ; error
 
        mov     al,bh           ; put high byte
        call    put_byte
        jc      $$1             ; error
 
$$1:    pop     bx
        ret
put_word  ENDP
 
;
;-----------dt_clear-------------------------John Mertus  January 89----
;
;   This stops the board and clears out all errors.
;  Notice that AX will be zero on no error, nonzero on error
;
        public  dt_clear
dt_clear PROC far
 
        call    dt_stop                 ; Stop board
        mov     ax,DT_CLEAR_CMD
        call    put_cmd
        ret
 
dt_clear ENDP
 
;
;-----------dt_reset-------------------------John Mertus  January 89----
;
;   This resets the entire board.  Should be called only once.
;  Notice that AX will be zero on no error, nonzero on error
;
        public  dt_reset
dt_reset  PROC far
 
        call    dt_stop
        mov     ax,DT_RESET_CMD
        call    put_cmd
        jc      $$1
        call    get_byte
        jc      $$1
;
;  clear the board
;
        mov     ax,DT_CLEAR_CMD
        call    put_cmd
        jc      $$1
 
$$1:    ret
 
dt_reset   ENDP
 
 
;
;-----------dt_error_local-------------------John Mertus  January 89----
;
;   This returns a 16 bit error in AX, if no error, AX is zero.
;
;   CX, DX are destroyed.
;
dt_error_local PROC
;
;  wait for some status
;
        mov     dx,cs:[DT_STATUS]       ; we want to check status
        mov     cx,TIME_DELAY           ; maximum counts
$$1:    in      al,dx                   ; get it
        mov     ah,al                   ; save it
        and     ah,Ready_Stat           ; ready bit set?
        jnz     $$2                     ; No
        loop    $$1
;
;  error
;
        mov     ax,8000h                ; error
        stc
        ret
;
;  no error
;
$$2:    and     al,Comp_Err             ; any error
        jnz     $$3                     ; yes
 
        xor     AX,AX                   ; clear ax
        clc
        ret
;
;  Read the error
;
$$3:    call    dt_stop
        mov     ax,DT_RDERROR_CMD
        call    put_cmd
        jc      $$4
        call    get_word                ; AX returns with word
 
$$4:    ret                             ; Go home
 
dt_error_local  ENDP
 
;
;-----------dt_error-------------------------John Mertus--January-89----
;
;   This is just like dt_error_local but is a far call.
;
        PUBLIC  dt_error
dt_error PROC  FAR
        call    dt_error_local
        ret
dt_error  ENDP
 
;
;-----------start_output-----------------------John Mertus---January-89---
;
;    This start the D/A output
;       Call from C
;               void da_start();
;
        PUBLIC  start_output
start_output    PROC FAR
 
        mov     ax,Start_D2A + Mode_DMA_Cont
        call    put_cmd
        ret
 
start_output    ENDP
 
;
;-----------AD_set_clock----------------------John Mertus---Dec 88---
;
;  This sets the clock to a sampling rate.
;
;    call from C is
;       AD_set_clock(unsigned srate, unsigned *psrate, int *f);
;
;    srate is the desired sampling rate
;    psrate is the physical sampling rate
;    f  is the clock tics for channel list conversion.
;
;    return is 0 for no error, otherwise the error word.
;
;
        PUBLIC  AD_set_clock
AD_set_clock  PROC FAR
        ARG     srate:word, nsrate:far ptr word, f:far ptr word
;
;  Find the physical sampling rate, this is srate*tics/list conversion
;
        mov     ax,[srate]              ; divisor
        mov     cl,cs:[CLOCKF]          ; clock factor
        xor     ch,ch                   ; 16 bit multiply
        mul     cx
;
;  Store factor in f
;
        les     bx,[f]
        mov     es:[bx],cx
;
;  Check for overflow
;
        cmp     dx,0
        je      $$0                     ; OK
        mov     ax,ERR_CLOCK            ; Flag it
        ret                             ; Go home
;
;  compute CLOCK_RATE/SRATE
;
$$0:    mov     cx,ax                   ; save it
        mov     ax,word ptr cs:[CLOCK_RATE]             ; low part
        mov     dx,word ptr cs:[CLOCK_RATE+2]           ; hi part
 
        div     cx                      ; divide by sampling rate
;
;  set the clock
;
        mov     bx,ax                   ; save tics
        mov     ax,DT_CLOCK_CMD
        call    put_cmd
        jc      $$1                     ; error
 
        mov     ax,bx                   ; restore tics
        call    put_word
        jc      $$1                     ; error
 
        call    dt_error_local          ; check for error
        cmp     ax,0                    ; error
        jne     $$1
;
; return the clock rate,  compute CLOCK_RATE/tics
;
        mov     ax,word ptr cs:[CLOCK_RATE]             ; low part
        mov     dx,word ptr cs:[CLOCK_RATE+2]           ; hi part
        div     bx
        les     bx,[nsrate]
        mov     es:[bx],ax                              ; return it
 
        xor     ax,ax                                   ; no error
$$1:    ret
 
AD_set_clock  ENDP
;
;-----------DA_set_clock----------------------John Mertus---Dec 88---
;
;  This sets the clock to a sampling rate.
;
;    call from C is
;       DA_set_clock(unsigned srate, unsigned *psrate, int *f);
;
;    srate is the desired sampling rate
;    psrate is the physical sampling rate
;    f  is the clock tics for channel list conversion.
;
;    return is 0 for no error, otherwise the error word.
;
;
        PUBLIC  DA_set_clock
DA_set_clock  PROC FAR
        ARG     srate:word, nsrate:far ptr word, f:far ptr word
;
;  Find the physical sampling rate, same as logical sampling rate
;
        mov     ax,[srate]              ; divisor
;
;  Store factor in f
;
        les     bx,[f]
        mov     word ptr es:[bx],1
;
;  compute CLOCK_RATE/SRATE
;
$$0:    mov     cx,ax                   ; save it
        mov     ax,word ptr cs:[CLOCK_RATE]             ; low part
        mov     dx,word ptr cs:[CLOCK_RATE+2]           ; hi part
 
        div     cx                      ; divide by sampling rate
;
;  set the clock
;
        mov     bx,ax                   ; save tics
        mov     ax,DT_CLOCK_CMD
        call    put_cmd
        jc      $$1                     ; error
 
        mov     ax,bx                   ; restore tics
        call    put_word
        jc      $$1                     ; error
 
        call    dt_error_local          ; check for error
        cmp     ax,0                    ; error
        jne     $$1
;
; return the clock rate,  compute CLOCK_RATE/tics
;
        mov     ax,word ptr cs:[CLOCK_RATE]             ; low part
        mov     dx,word ptr cs:[CLOCK_RATE+2]           ; hi part
        div     bx
        les     bx,[nsrate]
        mov     es:[bx],ax                              ; return it
 
        xor     ax,ax                                   ; no error
$$1:    ret
 
DA_set_clock  ENDP
 
;
;--------------DA_setup_DMA---------------------John Mertus---January 89---
;
;  This sets up the DMA buffers.  In this case, the address are
;  copied over together with the size.
;
;  C call is
;       int da_setup_DMA (int chan, int far *b, unsigned sz, int nu);
;  where chan is the channel (ignored here)
;  *b is an array of buffer addressed
;  size is the size in words of each buffer
;  nu is the number of buffer (must be 2)
;
        PUBLIC  DA_setup_DMA
DA_setup_DMA PROC FAR
        ARG     buffer:far PTR dword, sz:word, no:word ;
;
; setup the DMA mode registers
;
        mov     dx,DMA_Mode_Reg                 ; output DMA mode word
        mov     al,cs:[DMA_DA_Yes]
        out     dx,al
 
        mov     dx,DMA_Data_Reg                 ; Clear DMA flip/flop
        xor     al,al
        out     dx,al
 
        les     bx,[buffer]                     ; get buffer address
        les     ax,es:[bx]
;
;  16 bit base register is 16*(es % 0X1000) + ax
;
        push    ax
        mov     ax,es                           ; compute es % 0X1000
        xor     dx,dx                           ; dx is zero
        mov     bx,1000h
        div     bx                              ; dx is the mod 4096
        mov     cl,4                            ; multiply by 16
        shl     dx,cl
        pop     ax
        add     ax,dx                           ; ax is base register
 
        mov     dx,cs:[DMA_Base_Reg]
        out     dx,al                           ; send lower byte
        mov     al,ah
        out     dx,al                           ; and upper
 
        mov     ax,es                           ; get segment
        mov     cl,12
        shr     ax,cl                           ; divide by 0X1000
        mov     dx,cs:[DMA_Page_Reg]
        out     dx,al                           ; output page
 
        mov     dx,DMA_Chan_Reg                 ; output the DMA_Channel
        mov     ax,cs:[DMA_Channel]
        out     dx,al
;
;  Time to output the size
;
        mov     ax,[sz]
        shl     ax,1                            ; size of boundary
        mov     cs:[BOUNDARY],ax                ; save it
        shl     ax,1                            ; sz*4 - 1
        dec     ax
        mov     dx,cs:[DMA_Count_Reg]
        out     dx,al
        mov     al,ah
        out     dx,al
;
;  Set the output channel
;
        mov     ax,Set_D2A                      ; set it up
        call    put_cmd
        mov     al,cs:[SCHAN]
        call    put_byte
        mov     ax,5                            ; some dummy size
        call    put_word
        call    dt_error_local                  ; check error
;
;  restore critical registers and go home
;
        ret
DA_setup_DMA ENDP
 
;
;--------------AD_setup_DMA---------------------John Mertus---January 89---
;
;  This sets up the DMA buffers for A/D. In this case, the address are
;  copied over together with the size.
;
;  C call is
;       int ad_setup_DMA (int chan, int far *b, unsigned sz, int nu);
;  where chan is the channel (ignored here)
;  *b is an array of buffer addressed
;  size is the size in words of each buffer
;  nu is the number of buffer (must be 2)
;
        PUBLIC  AD_setup_DMA
AD_setup_DMA PROC FAR
        ARG     buffer:far PTR dword, sz:word, no:word;
;
; setup the DMA mode registers
;
        mov     dx,DMA_Mode_Reg                 ; output DMA mode word
        mov     al,cs:[DMA_AD_Yes]
        out     dx,al
 
        mov     dx,DMA_Data_Reg                 ; Clear DMA flip/flop
        xor     al,al
        out     dx,al
 
        les     bx,[buffer]                     ; get buffer address
        les     ax,es:[bx]
;
;  16 bit base register is 16*(es % 0X1000) + ax
;
        push    ax
        mov     ax,es                           ; compute es % 0X1000
        xor     dx,dx                           ; dx is zero
        mov     bx,1000h
        div     bx                              ; dx is the mod 4096
        mov     cl,4                            ; multiply by 16
        shl     dx,cl
        pop     ax
        add     ax,dx                           ; ax is base register
 
        mov     dx,cs:[DMA_Base_Reg]
        out     dx,al                           ; send lower byte
        mov     al,ah
        out     dx,al                           ; and upper
 
        mov     ax,es                           ; get segment
        mov     cl,12
        shr     ax,cl                           ; divide by 0X1000
        mov     dx,cs:[DMA_Page_Reg]
        out     dx,al                           ; output page
 
        mov     dx,DMA_Chan_Reg                 ; output the DMA_Channel
        mov     ax,cs:[DMA_Channel]
        out     dx,al
;
;  Time to output the size
;
        mov     ax,[sz]
        shl     ax,1                            ; size of boundary
        mov     cs:[BOUNDARY],ax                ; save it
        shl     ax,1                            ; sz*4 - 1
        dec     ax
        mov     dx,cs:[DMA_Count_Reg]
        out     dx,al
        mov     al,ah
        out     dx,al
;
;------------------------
; Set the A2D parameters
;------------------------
;
        mov     ax,Set_A2D                      ; set it up
        call    put_cmd
;
;  Output GAIN, start channel, end channel
;
        mov     al,cs:[GAIN]
        call    put_byte
        mov     al,cs:[SCHAN]
        call    put_byte
        mov     al,cs:[ECHAN]
        call    put_byte
        mov     ax,5                            ; some dummy size
        call    put_word
        call    dt_error_local                  ; check error
;
;  restore critical registers and go home
;
        ret
AD_setup_DMA ENDP
;
;-----------start_input-----------------------John Mertus---January-89---
;
;    This start the A/D output
;       Call from C
;               void ad_start();
;
        PUBLIC  start_input
start_input     PROC FAR
 
        mov     ax,Start_A2D + Mode_DMA_Cont
        call    put_cmd
        ret
 
start_input     ENDP
 
;
;------------DAD_error_msg--------------John Mertus-----Jan--89---
;
;   This returns a pointer to the error string.  The requested error
; comes from DAD_error();
;
;   C  call
;       char *DAD_error_msg(unsigned errnum, errmsg);
;
        PUBLIC  DAD_error_msg
DAD_error_msg PROC far
        ARG     errnum:word, errmsg:far PTR dword
 
        push    ds                      ; save the data area
        push    di
        push    si
 
        mov     ax,cs
        mov     ds,ax
;
;  copy the header string
;
        cld
        les     di,[errmsg]
        mov     si,offset ERROR_STRING
        mov     cx,14
        rep     movsb
        dec     di                              ; back up over null
;
;  get error
;
        mov     ax,[errnum]
;
;  Loop, copying each string depending upon the error
;
        mov     cx,16
        xor     bx,bx           ; clear bx
l7:     test    ax,1            ; is it a 1?
        jz      l8              ; no
;
;  copy index into table
;
        mov     si,ERR_TABLE[BX]
l9:     movsb
        cmp     byte ptr es:[di-1],0
        jnz     l9
        dec     di              ; backup over zero
;
;   end of copying loop
;
l8:     shr     ax,1            ; find next bit
        inc     bx              ; next offset in table
        inc     bx
        loop    l7
;
; restore critical areas and go home
;
        pop     si
        pop     di
        pop     ds
        ret
DAD_error_msg ENDP
;
;----------DAD_buf_status-------------John Mertus-------January--89--
;
;  This gives information about the buffer status of the current DMA count
;  The call from C is
;
;  unsigned c, int b
;
;               int DAD_bf_status(&c, &b)
;
;  Where c is the number of words that has been processed, b is the
;  current buffer being processed.  The error is returned, which in this
;  case is alway 0.
;
;
DAD_bf_status PROC FAR
        ARG  C:FAR PTR WORD, b:FAR PTR WORD
 
        call    DMA_count                       ; get count
        cmp     cs:[boundary],ax                ; where is it?
        jb      $$1                             ; in first buffer
;
;  count in second buffer, compute words processed
;
        mov     dx,cs:[boundary]                ; get boundary bytes
        sub     dx,ax                           ; bytes remaining
        mov     ax,dx
        shr     ax,1                            ; divide by 2
        mov     dx,1                            ; second buffer (1)
        jmp     $$2                             ; store it
;
;  count in first buffer, compute words procesed
;
$$1:    mov     dx,cs:[boundary]
        shl     dx,1                            ; multiply by 2
        sub     dx,ax                           ; bx is bytes left
        mov     ax,dx
        shr     ax,1                            ; divide by 2
        xor     dx,dx                           ; first buffer (0)
;
;  Store the results, ax is count, dx is buffer
;
$$2:    les     bx,[c]                          ; get count address
        mov     es:[bx],ax                      ; store it
 
        les     bx,[b]                          ; get buffer address
        mov     es:[bx],dx                      ; store it
;
; Check to see if status, then get error
;
        mov     dx,cs:[DT_STATUS]       ; we want to check status
        in      al,dx                   ; get it
        mov     ah,al                   ; save it
        and     ah,Ready_Stat           ; ready bit set?
        jne     $$3                     ; No
;
;  No error
;
        xor     ax,ax                           ; No error
        ret
;
; Report error
;
$$3:    call    dt_error_local                  ; check error
        ret                                     ; Go home
 
DAD_bf_status ENDP
 
;
;----------DMA_count------------------John Mertus-------January--89--
;
;  This just returns the DMA count of the #1 DMA channel.  This function
;  is very bad programming because it depends upon the speed of the 8086
;  in relationship to the DMA rate.  I can't think of a better way.  This
;  is another example of how the IBM PC sucks, since there is no interlock
;  for reading the DMA count.
;
;
DMA_count PROC
        mov     dx,cs:[DMA_Count_Reg]
;
;  Read a dma count
;
$$1:    in      al,dx
        mov     bh,al
        in      al,dx
        mov     bl,al
;
;  Read in another count
;
        in      al,dx
        mov     ah,al
        in      al,dx
;
;  If they agree, we are ok, if not try again
;
        cmp     bx,ax
        jnz     $$1
        xchg    al,ah
        ret
DMA_count ENDP
;
;--------------wait_on_buffer---------------------John Mertus---Dec 88---
;
;  This waits for a buffer to finish OR a fixed number of words
;  to transfer.
;
;  C call is
;       int wait_on_buffer (int bufnum, unsigned words);
;
;  bufnum is the buffer number (0 or 1)
;  words is number of transfers to wait on.
;
        PUBLIC  wait_on_buffer
wait_on_buffer PROC FAR
        ARG     bufnum:word, words:word
 
        mov     ax,[bufnum]             ; get buffer number
        cmp     ax,0                    ; is it zero
        jne     $$3                     ; no
;
;------------------------------------------------------------------
;  First buffer, wait for DMA count to go below the buffer boundary
;
 
$$1:    call    DMA_count
        cmp     cs:[boundary],ax        ; Have we exited buffer?
        ja      $$2                     ; yes
        mov     dx,cs:[boundary]
;
;  compute words transfered (faster ways to do the but WHO cares)
;
        shl     dx,1                            ; multiply by 2
        sub     dx,ax                           ; bx is bytes left
        mov     ax,dx
        shr     ax,1                            ; divide by 2
        cmp     ax,[words]                      ; how may words?
        jae     $$2                             ; yes enough
;
; Check to see if status, then get error if necessary
;
        mov     dx,cs:[DT_STATUS]       ; we want to check status
        in      al,dx                   ; get it
        mov     ah,al                   ; save it
        and     ah,Ready_Stat           ; ready bit set?
        jz      $$1                     ; No
 
        call    dt_error_local                  ; check error
        ret                                     ; Go home
;
;  Go home
;
$$2:    xor     ax,ax
        ret                             ; go home
;
;--------------------------------------------------------------------
;  second buffer, wait for DMA count to go above the buffer boundary
;
$$3:    call    DMA_count
        cmp     cs:[boundary],ax
        jb      $$4                     ; Done with buffer
;
; compute the words that have transfered
;
        mov     dx,cs:[boundary]        ; get boundary bytes
        sub     dx,ax                   ; bytes remaining
        mov     ax,dx
        shr     ax,1                    ; divide by 2
        cmp     ax,[words]
        jae     $$4                     ; Enought transfered
;
; Check to see if status, then get error if necessary
;
        mov     dx,cs:[DT_STATUS]       ; we want to check status
        in      al,dx                   ; get it
        mov     ah,al                   ; save it
        and     ah,Ready_Stat           ; ready bit set?
        jz      $$3                     ; No
 
        call    dt_error_local                  ; check error
        ret                                     ; Go home
 
$$4:    xor     ax,ax                   ; no error
        ret                             ; go home
 
wait_on_buffer ENDP
;
;-----------ad_set_channels-------------------John Mertus---January-89---
;
        PUBLIC  AD_set_channels
;
;  This emulates a channel gain list.  If the list cannot be convered,
; an error occures.
;  Call from C is
;       int ad_set_channels(int num, char list[], int s);
;
AD_set_channels  PROC FAR
 
        ARG     num:word, list: far ptr DWORD, s:word
        push    ds
;
; Get number of channels
;
        mov     cx,[num]
        mov     cs:[CLOCKF],cl          ; Save factor for clock
        lds     si,[list]               ; point to list
        cld
        dec     cx                      ; must have one value
        jge     $$1                     ; Ok
 
$$0:    mov     ax,ERROR_100            ; list error
        jmp     $$5
;
;  Store first channel, assume only one
;
$$1:    lodsb
        mov     cs:[SCHAN],al
        mov     cs:[ECHAN],al
        lodsb
        mov     cs:[GAIN],al
 
        cmp     cx,0
        jz      $$4                     ; No more to process
;
;  Check channel list, i.e if channels step by 1
;  and gain is the same.
;
$$2:    lodsb                           ; Get next channel
        dec     al
        cmp     cs:[ECHAN],al
        jne     $$0                     ; No error
        inc     al
        mov     cs:[ECHAN],al
        lodsb                           ; get gain
        cmp     cs:[GAIN],al            ; equal?
        je      $$3
;
; Flag error
;
        mov     ax,ERROR_200
        jmp     $$5
 
$$3:    loop    $$2
 
;
;  Check the type of input
;
        mov     ax,[s]
$$4:    xor     ax,ax                   ; no error
;
;  Common return
;
$$5:    pop     ds
        ret
 
AD_set_channels  ENDP
 
;
;-----------da_set_channels-------------------John Mertus---January-89---
;
        PUBLIC  da_set_channels
;
;  This emulates a channel gain list.  If the list cannot be convered,
; an error occures.
;  Call from C is
;       int da_set_channels(int num, int schan, int s);
;
DA_set_channels  PROC FAR
        ARG     num:word, chan:word, s:word
;
; Get number of channels
;
        mov     cx,[num]
;
; Make sure we are in the range
;
        cmp     cx,0
        jle     $$1
 
        cmp     cx,2
        jle     $$2
;
;  Report error
;
$$1:    mov     ax,ERROR_100            ; flag error
        ret                             ; return
;
;  Number of channels ok, check start
;
$$2:    mov     cs:[CLOCKF],cl          ; Save factor for clock
        mov     ax,[chan]               ; get channel
        cmp     ax,0
        jl      $$1
        cmp     ax,1
        jg      $$1
;
;  Override channel if both
;
        cmp     cl,2
        jne     $$3
        mov     al,2
 
$$3:    mov     cs:[SCHAN],al           ; Save start channel
;
;  Set the output channel
;
        mov     ax,Set_D2A                      ; set it up
        call    put_cmd
        mov     al,cs:[SCHAN]
        call    put_byte
        mov     ax,5                            ; some dummy size
        call    put_word
        call    dt_error_local                  ; check error
        ret
 
DA_set_channels  ENDP
;
;  end of entire program
;
        END DUMMY                       ; of program
