;******************************************************************;
;* Ассемблерный драйвер к плате L-154, содержащий интерфейсные	  *;
;* функции  взаимодействия  с  платой  L-154   для   программ,    *;
;* написанных на языке 'Си'.         				  *;
;*                                                     		  *;
;* Драйвер написан на языке Турбо Ассемблер (TASM 2.0).		  *;
;*                                                                *;
;*   L-card SoftWare Lab limited, Moscow, 09.01.94                *;
;*       tel (095) 257-17-10, fax (095) 257-10-90		  *;
;******************************************************************;

; для компилирования для использования драйвера в программах на языке
; Паскаль необходимо заменить следующую строку на :
; .MODEL TPASCAL
; и заменить в данном файле строку 'C' на 'PASCAL'
.MODEL LARGE			; все нижеприведенные функции и используемые
				; указатели имеют тип FAR

;*****************************************************
; ОБЪЯВЛЕНИЕ ФУНКЦИЙ, ЭКСПОРТИРУЕМЫХ В 'Си'

.CODE

; Функции общего назначения:
   PUBLIC   C SETBASEADDRESS	; установка нового базового адреса платы
				; в адресном пространстве PC
   PUBLIC   C PLATA_TEST	; тестирование наличия платы

; Функции по работе с аналого-цифровыми каналами
   PUBLIC   C SETCHANNEL	; установка номера канала
   PUBLIC   C SAMPLE  		; однократный ввод с АЦП
   PUBLIC   C ADCHAN  		; однократный ввод с переустановкой канала
   PUBLIC   C KADR  		; однократный многоканальный ввод с АЦП
   PUBLIC   C STREAM  		; одноканальный ввод в программном режиме
   PUBLIC   C SOFT  		; многоканальный ввод в программном режиме

; Функции по работе с цифро-аналоговыми каналами
   PUBLIC   C OUTDA		; однократная запись на ЦАП
   PUBLIC   C OUTDAARRAY	; одноканальный вывод на ЦАП с программной
				; синхронизацией

; Функции по работе с цифровыми (дискретными) ТТЛШ линиями
   PUBLIC   C INPBYTE		; чтение цифровых линий
   PUBLIC   C OUTBYTE		; запись цифровых линий

; Функции по работе с прерываниями
   PUBLIC   C INITINTR		; инициализация режима прерываний
   PUBLIC   C STOP_INTR		; выключение прерываний
   PUBLIC   C INTR_SETUP	; выбор номера прерывания (IRQ3..IRQ6)
   PUBLIC   C RESET_IRQ		; сброс прерываний

; Функции для работы с таймерами
   PUBLIC   C TIMER		; программирование таймеров
   PUBLIC   C WAIT_TIMER	; ожидания перехода 1-0 на таймере
   PUBLIC   C GET_TIMER		; возвращает состояние таймеров

;***********************************************************
; ОПИСАНИЕ ПЕРЕМЕННЫХ С АДРЕСАМИ ПОРТОВ

; адреса портов платы L-154 ( W -> означает запись, R -> означает чтение)

DataReg	 dw    300h             ; чтение/запись данных на/с АЦП/ЦАП  (R/W)
Status   dw    302h             ; биты таймера и ьит готовности      (R)
Channel	 dw    302h             ; установка номера канала            (W)
Digits   dw    303h             ; чтение/запись цифровых ТТЛШ линий  (R/W)
Start    dw    304h	        ; старт преобразования АЦП ()        (W)
T1	 dw    308h             ; частота первого таймера            (W)
T2	 dw    309h             ; частота второго таймера            (W)
T3	 dw    30ah             ; частота третьего таймера           (W)
Tport	 dw    30bh             ; выбор режима таймеров
InterEn  dw    30fh	        ; разрешение прерываний (0 -> запрет) (W)

;************* Определение локальных переменных *****
OldHand  dd	0		; адрес старого обработчика IRQ3
OldMask	 db	0		; старая маска контроллера прерываний
IrqNumb	 dw  0
IrqN	 db  0bh
IrqMaskBit db  0f7h

;****************************************************
; ТЕКСТЫ ВЫШЕОПИСАННЫХ ФУНКЦИЙ

;*****************************************************************************
;* ФУНКЦИЯ:  void SETBASEADDRESS(int address)                                *
;* устанавливает новый базовый адрес в пространстве PC для функций данного   *
;* драйвера, переменная address должна быть равна 0x300, 0x310, 0x330, 0x340 *
;*****************************************************************************

SETBASEADDRESS PROC C FAR Address : WORD
	mov	ax, address
	mov	cs:DataReg, ax

        add     ax, 2
        mov     cs:Status, ax
        mov     cs:Channel, ax

        inc     ax
        mov     cs:Digits, ax

        inc     ax
	mov     cs:Start, ax

        mov     ax, address
        add     ax, 8
        mov     cs:T1, ax

        inc     ax
        mov     cs:T2, ax

        inc     ax
        mov     cs:T3, ax

        inc     ax
        mov     cs:Tport, ax

        mov     ax, address
        add     ax, 0fh
        mov     cs:InterEn, ax
	ret
ENDP

;*********************************************************************
;*                  ФУНКЦИЯ : int PLATA_TEST()                       *
;*  возвращает нулевое значение в случае успешного тестирования      *
;*  присутствия платы L-154 и ненулевое значение в противном случае  *
;*  Нулевое значение означает, что либо плата отсутствует в          *
;*  компьютере, либо функции SETBASEADDRESS  был передан базовый     *
;*  адрес, не соответствующий установленному на плате.               *
;*********************************************************************

PLATA_TEST PROC  C FAR
	mov     ax, 20                  ; установим частоту
        mov     bx, 0
	call    set_rate

	mov	cx, 20
 Test3:
	mov	dx, cs:Status           ; порт с выходои таймера
	mov	bx, 0FFFFh		; установим таймаут
Test0:  dec	bx
	jz	test_tmout		; проверим на атймаут
	in	al,dx
	test	al, 1			; проверим таймер
	jz	Test0
	mov	bx, 0FFFFh		; установим таймаут
Test1:  dec	bx
	jz	test_tmout		; проверим на таймаут
	in	al,dx
	test	al, 1			; проверим таймер
	jnz	Test1
	loop    Test3
	mov	ax, 0
	ret
  test_tmout:
	mov	ax, 1
	ret
ENDP


;*******************************************************************
;*                  ФУНКЦИЯ : void SETCHANNEL(int Channel)         *
;*   устанавливает канал Channel для последующего ввода            *
;*******************************************************************

SETCHANNEL PROC  C FAR Chan : WORD
	mov	ax, Chan		; установим номер канала
	call	set_chan
	ret
ENDP

;********************************************************************
;*                  ФУНКЦИЯ ; int SAMPLE()                          *
;*   возвращает результат преобразования по ранее заданному каналу  *
;********************************************************************

SAMPLE PROC C FAR
	mov	dx, cs:Start		; дадим старт преобразования
	out	dx, al
        call    wait_bit_ready          ; дождемся бита готовности
	mov	dx, cs:DataReg          ; считаем данные
	in	ax, dx
	sub	ax, 2048
	ret
ENDP

;*******************************************************************
;*                  ФУНКЦИЯ : int ADCHAN(int Channel)              *
;*   возвращает результат преобразования по каналу 'Channel'       *
;*******************************************************************

ADCHAN PROC  C FAR Chan : WORD
	mov	ax, Chan		; установим номер канала
	call	set_chan
        call    wait_chan               ; подождем установления номера канала
	mov	dx, cs:Start		; дадим старт преобразования
	out	dx, al
	call	wait_bit_ready          ; дождемся бита готовности
	mov	dx, cs:DataReg	        ; считаем данные
	in	ax, dx
	sub	ax, 2048
	ret
ENDP

;*******************************************************************
;*      ФУНКЦИЯ : void KADR (int *Data, int *Channels,             *
;*                               int Nch);            		   *
;* вводит Nch отсчетов с каналов АЦП, указанных в массиве Channels *
;* в массив Data
;*******************************************************************
Kadr PROC C FAR Datad : DWORD, Chans : DWORD, Nch : WORD

	push    ds                     ; сохраним регистры
	push    si
	push	di

	mov     cx, Nch			; число каналов
	les	di, Datad		; адрес массива с данными
	lds	si, Chans		; адрес массива с номерами каналами

    KadrLab1:
	lodsw                           ; установим номер канала
	call	set_chan
        call    wait_chan               ; подождем установления номера канала
	mov	dx, cs:Start		; дадим старт преобразования
	out	dx, al
	call	wait_bit_ready          ; дождемся бита готовности
	mov	dx, cs:DataReg	        ; считаем данные
	in	ax, dx                  ; считаем младший байт
	sub     ax, 2048
	stosw
	loop     KadrLab1	        ; цикл по числу отсчетов

	pop	di                      ; восстановим регистры
	pop     si
	pop     ds
	ret
ENDP

;*************************************************************************
;* ФУНКЦИЯ: void STREAM (int *Data, int Npoint, int Channel, int Rate)   *
;*								         *
;*        считывание 'Npoint' отсчетов по каналу 'Channel' в массив      *
;*                   'Data' с интервалом 'Rate' мкс между отсчетами      *
;*************************************************************************
STREAM PROC  C FAR  Datad:DWORD, Npoint:WORD, Chan:WORD, Rate:WORD

	push 	di              	; сохраним регистры
	push	si

	mov	ax, Chan		; установим номер канала
	call	set_chan

	mov	ax, Rate		; установим частоту ввода
        mov     bx, 0                   ; на первом таймере
	call	set_rate

	; подготовка счетчиков и указателей
	les	di, Datad		; адрес буфера данных
	mov	cx, Npoint		; число вводимых отсчетов

	; перенесем адрес порта данных из памяти в регистры для
	; ускорения работы программы
	mov	si, cs:DataReg

	cli                             ; отключим прерывания

;  *************** ОСНОВНОЙ ЦИКЛ ПО ЧИСЛУ ОТСЧЕТОВ *******************
 StrMainCyc:
        ; дождемся прихода меандра
        mov        dx, cs:Status
    StreamLab3:                         ; ждем нулевой уровень
        in       al, dx
        and      al, 1
	jnz      StreamLab3

    StreamLab4:
	in       al, dx                 ; ждем единичный уровень
	and      al, 1
	jz       StreamLab4

	mov	dx, cs:Start		; дадим старт преобразования
	out	dx, al
	call	wait_bit_ready          ; дождемся бита готовности
	mov	dx, si                  ; адрес порта чтения данных
	in	ax, dx                  ; считаем данные
	sub     ax, 2048
	stosw				; запишем в память
	loop StrMainCyc			; цикл по числу отсчетов

	sti				; разрешим прерывания

	pop	si			; восстановим регистры
	pop	di
	ret
ENDP

;*************************************************************************
;* ФУНКЦИЯ: void SOFT(int *Data, int Nkadr, int *Channels, int Nch,      *
;*		      int Rate) 			 	 	 *
;*								         *
;*     считывание  'Nkadr'  кадров  в  массив  'Data'  с интервалом      *
;*    'Rate' мкс между кадрами.  Под  кадром   подразумевается  ряд      *
;*     отсчетов, состоящий из  результатов последовательного опроса      *
;*     первых  'Nch'  каналов,  указанных в массиве 'Channels'.          *
;*     Размерность  массива  'Data'  не  должна  превышать 32500.        *
;*************************************************************************
SOFT PROC  C FAR  Datad:DWORD, Nkadr:WORD, Chan:DWORD, Nch:WORD, Rate:WORD

	push	ds			; сохраним используемые регистры
	push 	di
	push	si

	cli				; запретим прерывания

	mov	ax, Rate		; установим частоту ввода
        mov     bx, 0                   ; на первом таймере
	call	set_rate

        ; проинициализируем счетчики
        mov	cx, Nkadr	        ; число кадров
        mov	bx, Nch	                ; число каналов в кадре
	les	di, Datad		; адрес массива с данными

        ; ОСНОВНОЙ ЦИКЛ ПО ЧИСЛУ КАДРОВ
     mnlp:
        push    cx			; сохраним счетчик числа кадров
        lds	si, Chan                ; загрузим адрес массива с номерами каналов
        mov	dx, cs:Channel	        ; адрес порта задания номера канала
        lodsw			        ; считаем номер кнала
        out	dx,al		        ; установим канал

        mov	dx, cs:Status	        ; адрес порта с выходами таймеров
    mnlp0:	in	al, dx          ; дождемся перехода по таймеру 0
        test	al, 1
        jz	mnlp0
    mnlp1:
        in	al, dx
        test	al,1
        jnz	mnlp1

        mov	cx, bx

        ; ЛОКАЛЬНЫЙ ЦИКЛ ПО ЧИСЛУ КАНАЛОВ
     mlb:
        mov	dx, cs:Start  	        ; дадим старт на АЦП
        out	dx,al

        jmp	short $+2	        ; чуть-чуть подождем
        jmp	short $+2
        jmp	short $+2

        mov	dx, cs:Status	        ; ждем бита готовности
     sompl1:
        in 	al, dx
        test	al, 8
        jnz	sompl1

	mov	dx, cs:DataReg
	in	ax,dx			; читаем старший байт данных
	sub	ax, 2048
	stosw			        ; запишем данные в буфер

	lodsw			        ; загрузим номер следующего канала
	mov	dx, cs:Channel
	out	dx,al	                ; установим номер канала

	call    wait_chan
	loop	mlb		        ; цикл по числу каналов

	pop	cx		        ; восстановим счетчик числа кадров

        loop	mnlp		        ; цикл по числу кадров

        cli

	pop	si			; восстановим регистры
	pop	di
	pop	ds
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:  void OUTDA(int Code)                                          *
;* устанавливает выходное напряжение цифро-аналоговых преобразователей     *
;*                                                                         *
;*    'Code' - выходное напряжение с шагом 2.5 мВ                          *
;*	     0    -   -5.12 V                                              *
;*	   2048   -    0.00 V                                              *
;*	   4095   -   +5.12 V                                              *
;***************************************************************************

OUTDA PROC C FAR Coded : WORD
	mov	dx, cs:DataReg
	mov	ax, Coded      		; передадим младший байт
	out	dx, ax
	ret
ENDP

;*******************************************************************
;*      ФУНКЦИЯ : int OUTDAARRAY(int *ptr, int Rate,int Amount);   *
;*   выводит Amount отсчетов из массива ptr на ЦАП                 *
;*******************************************************************
OutDaArray PROC C FAR Datad : DWORD, Rate : WORD, N : WORD,

	push    ds                     ; сохраним регистры
	push    si

	mov     ax, Rate                ; установим частоту
        mov     bx, 0                   ; на первом таймере
	call    set_rate

	mov     cx, N
	lds	si, Datad		; адрес массива с данными

    OutdaArrLab1:
	lodsw                           ; считаем данные

	mov	dx, cs:DataReg
	out	dx, ax                  ; выдадим данные на ЦАП

       ; дождемся прихода меандра
       mov        dx, cs:Status
   OutDaArrLab3:                        ; ждем нулевой уровень
       in       al, dx
       and      al, 1
       jnz      OutDaArrLab3

   OutDaArrLab4:
       in       al, dx                  ; ждем единичный уровень
       and      al, 1
       jz       OutDaArrLab4

       loop     OutdaArrLab1	        ; цикл по числу отсчетов

       pop     si                      	; восстановим регистры
       pop     ds
       ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:  int INPBYTE()                                                 *
;*    возвращает состояние 8-и внешних цифровых TTL линий                  *
;***************************************************************************

INPBYTE PROC C FAR
	mov	dx, cs:Digits		; цифровой порт
	in	al, dx
	mov	ah, 0
	ret
ENDP


;***************************************************************************
;* ФУНКЦИЯ:  void OUTBYTE(int Code)                                        *
;*        функция установки внешних цифровых линий                         *
;*       'Code' - соответствующий 8-и битный код                           *
;***************************************************************************

OUTBYTE PROC C FAR CodeVal : WORD
	mov	dx, cs:Digits		; цифровой порт
	mov	ax, CodeVal
	out	dx, al
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:    void INITINTR(int Rate1, int Rate2, int *Vector)            *
;*    программирует контроллер прерываний и плату L154 для                 *
;*    работы в режиме генерации прерывания IRQ3 (номер 0xb)                *
;***************************************************************************

INITINTR PROC  C FAR Rate1 : WORD, Rate2 : WORD, Vector : DWORD

	push	ds			; сохраним регистры
	push	si
	push	di

      ; запретим прерывания на плате
        mov      dx, cs:InterEn
        mov      al, 0
        out      dx, al

      ; запомним старую маску прерываний
	in	al, 21H
	mov	byte ptr cs:OldMask, al

      ; запомним старый  вектор прерывания
	mov	ah, 35h
	mov	al, cs:IrqN
	int	21h
	mov     word ptr cs:OldHand[0], bx
	push	es
	pop	ax
	mov     word ptr cs:OldHand[2], ax

      ; установим вектор прерывания
	mov	ah, 25h
	mov	al, cs:IrqN
	lds	dx, Vector
	int	21h

      ; проинициализируем контроллер прерываний
	in      al, 21h
	mov	bl, cs:IrqMaskBit
	and	al, bl
	out     21h, al                ; установим маску прерываний

	mov   al, 20h
	out   20h, al

      ; запрограммируем плату
	mov     ax, Rate1   		; установим частоту
	mov     bx, 0
	call    set_rate

	mov     ax, Rate2		; установим частоту
	mov     bx, 1
	call    set_rate

      ; разрешим прерывания на плате
        mov      dx, cs:InterEn
        mov      al, 1
        out      dx, al

	pop	di			; восстановим регистры
	pop	si
	pop	ds
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:    void STOP_INTR(void)         		   		   *
;* выключаем режим генерации прерываний на плате L-154 и восстанавливает  *
;* контроллер прерываний PC                				   *
;***************************************************************************

STOP_INTR PROC  C FAR

	push	ds			; сохраним регистры
	push	si
	push	di

	cli

      ; запретим генерацию прерываний на плате L-154
        mov      dx, cs:InterEn
        mov      al, 0
        out      dx, al

	mov	al, 20h
	out     20h, al

      ; восстановим старый  вектор прерывания
	mov     dx, word ptr cs:OldHand[0]
	mov     ax, word ptr cs:OldHand[2]
	push	ax
	pop	ds
	mov	ah, 25h
	mov	al, cs:IrqN
	int	21h

      ; восстановим контроллер прерываний
	mov	al, byte ptr cs:OldMask
	out     21h, al

	sti

	pop	di			; восстановим регистры
	pop	si
	pop	ds
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:    void INTR_SETUP(int number)         		   	   *
;* данная функция сообщает драверу о номере прерывания IRQ, установленному *
;*  с помощью перемычек на плате L-154 (см. описание платы)                *
;*  number = 0    IRQ3                                                     *
;*  number = 1    IRQ4                                                     *
;*  number = 2    IRQ5                                                     *
;*  number = 3    IRQ6                                                     *
;***************************************************************************
INTR_SETUP PROC  C FAR Number : WORD
	mov	ax, Number
	mov	cs:IrqNumb, ax
	mov	bx, 0Bh
	add	bx, ax
	mov	cs:IrqN, bl
	mov	cx, Number
	mov	al, 8
	shl	al, cl
	xor	al, 0ffh
	mov	cs:IrqMaskBit, al
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:    void RESET_IRQ(void)         		   		   *
;* сбрасывает контроллер прерываний                                        *
;***************************************************************************
RESET_IRQ PROC  C FAR
	mov	al, 020h
	out	020h, al
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:  void TIMER(int Timer, int Mode, int Rate)                     *
;*     функция программирования таймера                                    *
;*    'Timer' - номер таймера (0, 1, 2)                                    *
;*    'Mode'  - режим таймера (0-5)                                        *
;*    'Rate'  - время в мкс                                                *
;***************************************************************************

TIMER PROC C FAR Tmr : WORD, ModeD : WORD, Rate : WORD
	cli

	mov	bl,30h
	mov	ax,Tmr 		; считаем номер таймера
	mov	cl,6
	shl	al,cl
	or	bl,al

	mov	ax,ModeD	; считаем устанавливаемый режим таймера
	shl	al,1
	or	al,bl
	mov	dx,cs:Tport
	out	dx,al

	mov	ax,Tmr        	; считаем номер таймера
	mov	dx,cs:T1
	add	dx,ax

	mov	ax,Rate  	; считаем время
	out	dx,al

        push	dx
        mov	dx, cs:Digits		; delay
        in	al, dx
        pop	dx

	mov	al,ah
	out	dx,al

	sti
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:    void WAIT_TIMER(int Timer)                                   *
;*             функция ожидания перехода 1-0 на таймере 'Timer'		   *
;***************************************************************************
WAIT_TIMER PROC C FAR NTimer : WORD
	mov     bx, NTimer
	inc     bx
	mov	dx, cs:Status
W_T0:	in	al,dx
	test	al,bl
	jz	W_T0
W_T1:	in	al,dx
	test	al,bl
	jnz	W_T1
	ret
ENDP

;***************************************************************************
;* ФУНКЦИЯ:  int GET_TIMER()                                    	   *
;*        функция чтения порта статуса с выходами таймеров и битом         *
;* 	  готовности         						   *
;***************************************************************************

GET_TIMER PROC C FAR
        mov	dx, cs:Status
        in	al, dx
	ret
ENDP

;************** ЛОКАЛЬНЫЕ (НЕ ЭКСПОРТИРУЕМЫЕ В 'Си') ФУНКЦИИ **************

;********* локальная функция ожидания бита готовности
wait_bit_ready proc near
	jmp $+2
	jmp $+2
	mov	dx, cs:Status
 str_lp2:
	in	al, dx
	and	al, 8
	jnz	str_lp2                 ; ожидаем бит готовности
	ret
endp


;********* локальная функция установки номера канала
set_chan  proc near
	mov	dx, cs:Channel		; порт установки номера канала
	out	dx, al			; выведем значение канала
	ret
endp

;********* локальная функция ожидания установки номера канла
wait_chan  proc near
	mov	dx, cs:Start
        in	al, dx
	ret
endp

;********** локальная функция установки частоты ввода
; ax -> Rate
; bx -> Timer
set_rate  proc near
        push    cx
        push    ax
        push    bx
	mov     ax, bx
	mov	bl,30h
	mov	cl,6
	shl	al,cl
	or	bl,al

	mov	ax,6    	; считаем устанавливаемый режим таймера
	or	al,bl
	mov	dx,cs:Tport
	out	dx,al

        pop     bx

	mov	dx,cs:T1
	add	dx, bx

        pop     ax
	out	dx,al

        push	dx
	mov	dx, cs:Start		; delay
	in	al, dx
	pop	dx

	mov	al,ah
	out	dx,al

	pop     cx
	ret
endp

END
