[Назад] [Далее]

5.10.2. Последовательный порт

Каждый из последовательных портов обменивается данными с процессором через набор портов ввода-вывода: СОМ1 = 03F8h – 03FFh, COM2 = 02F8h – 02FFh, COM3 = 03E8H – 03EFh и COM4 = 02E8h – 02EFh. Имена портов СОМ1 – COM4 на самом деле никак не зафиксированы. BIOS просто называет порт СОМ1, адрес которого (03F8h по умолчанию) записан в области данных BIOS по адресу 0040h:0000h. Точно так же порт COM2, адрес которого записан по адресу 0040h:0002h, COM3 — 0040h:0004h и COM4 — 0040h:0006h. Рассмотрим назначение портов ввода-вывода на примере 03F8h – 03FFh.

Итак, первое, что должна сделать программа, работающая с последовательным портом, — проинициализировать его, выполнив запись в регистр управления линией (03FBh) числа 80h, запись в порты 03F8h и 03F9h делителя частоты, снова запись в порт 03FBh с нужными битами, а также запись в регистр разрешения прерываний (03F9h) для выбора прерываний. Если программа вообще не пользуется прерываниями — надо записать в этот порт 0.

Перед записью данных в последовательный порт можно проверить бит 5, а перед чтением — бит 1 регистра состояния линии, но, если программа использует прерывания, эти условия выполняются автоматически. Вообще говоря, реальная серьезная работа с последовательным портом возможна только при помощи прерываний. Посмотрим, как может быть устроена такая программа на следующем примере:

; term2.asm
; Минимальная терминальная программа, использующая прерывания
; Выход - Alt-X

        .model     tiny
        .code
        .186
        org        100h                 ; СОМ-программа

; следующие четыре директивы определяют, для какого последовательного порта
; скомпилирована программа (никаких проверок не выполняется - не запускайте этот
; пример, если у вас нет модема на соответствующем порту). Реальная программа
; должна определять номер порта из конфигурационного файла или из командной
; строки
COM                equ    02F8h         ; номер базового порта (COM2)
IRQ                equ    0Bh           ; номер прерывания (INT 0Bh для IRQ3)
E_BITMASK          equ    11110111b     ; битовая маска для разрешения IRQ3
D_BITMASK          equ    00001000b     ; битовая маска для запрещения IRQ3

start:
        call       init_everything      ; инициализация линии и модема
main_loop:                              ; основной цикл
; реальная терминальная программа в этом цикле будет выводить данные из буфера
; приема (заполняемого из обработчика прерывания) на экран, если идет обычная
; работа, в файл, если пересылается файл, или обрабатывать как-то по-другому.
; В нашем примере мы используем основной цикл для ввода символов, хотя лучше это
; делать из обработчика прерывания от клавиатуры
        mov        ah,8                 ; Функция DOS 08h
        int        21h                  ; чтение с ожиданием и без эха,
        test       al,al                ; если введен обычный символ,
        jnz        send_char            ; послать его,
        int        21h                  ; иначе - считать расширенный ASCII-код,
        cmp        al,2Dh               ; если это не Alt-X,
        jne        main_loop            ; продолжить цикл,
        call       shutdown_everything  ; иначе - восстановить все в
                                        ; исходное состояние
        ret                             ; и завершить программу

send_char:                              ; посылка символа в модем
; Реальная терминальная программа должна здесь только добавлять символ в буфер
; передачи и, если этот буфер был пуст, разрешать прерывания "регистр передачи
; пуст". Просто пошлем символ напрямую в порт
        mov        dx,COM               ; регистр THR
        out        dx,al
        jmp        short main_loop

old_irq dd         ?         ; здесь будет храниться адрес старого обработчика

; упрощенный обработчик прерывания от последовательного порта
irq_handler        proc    far
        pusha                           ; сохранить регистры
        mov        dx,COM+2             ; прочитать регистр идентификации
        in         al,dx                ; прерывания
repeat_handler:
        and        ax,00000110b         ; обнулить все биты, кроме 1 и 2,
        mov        di,ax                ; отвечающие за 4 основные ситуации
        call       word ptr cs:handlers[di] ; косвенный вызов процедуры
                                            ; для обработки ситуации
        mov        dx,COM+2             ; еще раз прочитать регистр идентификации
        in         al,dx                ; прерывания,
        test       al,1                 ; если младший бит не 1,
        jz         repeat_handler       ; надо обработать еще одно прерывание,
        mov        al,20h               ; иначе - завершить аппаратное прерывание
        out        20h,al               ; посылкой команды EOI (см. главу 5.10.10)
        рора
        iret
; таблица адресов процедур, обслуживающих разные варианты прерывания
handlers           dw    offset line_h, offset trans_h
                   dw    offset recv_h, offset modem_h

; эта процедура вызывается при изменении состояния линии
line_h  proc       near
        mov        dx,COM+5             ; пока не будет прочитан LSR,
        in         al,dx                ; прерывание не считается завершившимся
; здесь можно проверить, что случилось, и, например, прервать связь, если
; обнаружено состояние BREAK
        ret
line_h  endp
; эта процедура вызывается при приеме новых данных
recv_h  proc       near
        mov        dx,COM               ; пока не будет прочитан RBR,
        in         al,dx                ; прерывание не считается завершившимся
; здесь следует поместить принятый байт в буфер приема для основной программы,
; но мы просто сразу выведем его на экран
        int        29h                  ; вывод на экран
        ret
recv_h  endp
; эта процедура вызывается по окончании передачи данных
trans_h proc       near
; здесь следует записать в THR следующий символ из буфера передачи и, если
; буфер после этого оказывается пустым, запретить этот тип прерывания
        ret
trans_h endp
; эта процедура вызывается при изменении состояния модема
modem_h proc       near
        mov        dx,COM+6             ; пока MCR не будет прочитан,
        in         al,dx                ; прерывание не считается завершившимся
; здесь можно определить состояние звонка и поднять трубку, определить
; потерю несущей и перезвонить, и т.д.
        ret
modem_h endp
irq_handler        endp

; инициализация всего, что требуется инициализировать
init_everything    proc    near
; установка нашего обработчика прерывания
        mov        ax,3500h+IRQ         ; АН = 35h, AL = номер прерывания
        int        21h                  ; получить адрес старого обработчика
        mov        word ptr old_irq,bx  ; и сохранить в old_irq
        mov        word ptr old_irq+2,es
        mov        ax,2500h+IRQ         ; AH = 25h, AL = номер прерывания
        mov        dx,offset irq_handler ; DS:DX - наш обработчик
        int        21h                  ; установить новый обработчик
                                        ; сбросить все регистры порта
        mov        dx,COM+1             ; регистр IER
        mov        al,0
        out        dx,al                ; запретить все прерывания
        mov        dx,COM+4             ; MCR
        out        dx,al                ; сбросить все линии модема в О
        mov        dx,COM+5             ; и выполнить чтение из LSR,
        in         al,dx
        mov        dx,COM+0             ; из RBR
        in         al,dx
        mov        dx,COM+6             ; и из MSR
        in         al,dx                ; на тот случай, если они недавно
                                        ; изменялись,
        mov        dx,COM+2             ; а также послать 0 в регистр FCR,
        mov        al,0                 ; чтобы выключить FIFO
        out        dx,al

; установка скорости СОМ-порта
        mov        dx,COM+3             ; записать в регистр LCR
        mov        al,80h               ; любое число со старшим битом 1
        out        dx,al
        mov        dx,COM+0             ; теперь записать в регистр DLL
        mov        al,2                 ; младший байт делителя скорости,
        out        dx,al
        mov        dx,COM+1             ; а в DLH -
        mov        al,0                 ; старший байт
        out        dx,al                ; (мы записали 0002h -
                                        ; скорость порта 57 600)
; инициализация линии
        mov        dx,COM+3             ; записать теперь в LCR
        mov        al,0011b             ; число, соответствующее режиму 8N1
        out        dx,al                ; (наиболее часто используемому)
; инициализация модема
        mov        dx,COM+4             ; записать в регистр MCR
        mov        al,1011b             ; битовую маску, активирующую DTR, RTS
        out        dx,al                ; и OUT2
; здесь следует выполнить проверку на наличие модема на этом порту (читать
; регистр MSR, пока не будут установлены линии CTS и DSR или не кончится время),
; а затем послать в модем (то есть поместить в буфер передачи) инициализирующую
; строку, например "ATZ",0Dh

; разрешение прерываний
        mov        dx,COM+1             ; записать в IER - битовую маску,
        mov        al,1101b             ; разрешающую все прерывания, кроме
                                        ; "регистр передачи пуст"
        out        dx,al
        in         al,21h               ; прочитать OCW1 (см. главу 5.10.10)
        and        al,E_BITMASK         ; размаскировать прерывание
        out        21h,al               ; записать OCW1
        ret
init_everything    endp

; возвращение всего в исходное состояние
shutdown_everything   proc   near
; запрещение прерываний
        in         al,21h               ; прочитать OCW1
        or         al,D_BITMASK         ; замаскировать прерывание
        out        21h,al               ; записать OCW1
        mov        dx,COM+1             ; записать в регистр IER
        mov        al,0                 ; ноль
        out        dx,al                ; сброс линий модема DTR и CTS
        mov        dx,COM+4             ; записать в регистр MCR
        mov        al,0                 ; ноль
        out        dx,al                ; восстановление предыдущего
                                        ; обработчика прерывания
        mov        ax,2500h+IRQ         ; АН = 25h,  AL = номер прерывания
        lds        dx,old_irq           ; DS:DX - адрес обработчика
        int        21h
        ret
shutdown_everything   endp
        end         start

п»ї
"target=_blank><\/a>") //-->