| [Назад] [Далее] | ![]() |
Каждый из последовательных портов обменивается данными с процессором через набор портов ввода-вывода: СОМ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.
03F8h для чтения и записи — если старший бит регистра управления линией = 0, это — регистр передачи данных (THR или RBR). Передача и прием данных через последовательный порт соответствуют записи и чтению именно в этот порт.
03F8h для чтения и записи — если старший бит регистра управления линией = 1, это — младший байт делителя частоты порта.
03F9h для чтения и записи — если старший бит регистра управления линией = 0, это — регистр разрешения прерываний (IER):
бит 3: прерывание по изменению состояния модема
бит 2: прерывание по состоянию BREAK или ошибке
бит 1: прерывание, если буфер передачи пуст
бит 0: прерывание, если пришли новые данные
03F9h для чтения и записи — если старший бит регистра управления линией = 1, это — старший байт делителя частоты порта. Значение скорости порта определяется по значению делителя частоты (табл. 20).
Таблица 20. Делители частоты последовательного порта
| Делитель частоты | Скорость |
| 0000h | 115 200 |
| 0001h | 57 600 |
| 0002h | 38 400 |
| 0006h | 19 200 |
| 000Ch | 9 600 |
| 0010h | 7 200 |
| 0018h | 4 800 |
| 0020h | 3 600 |
| 0030h | 2 400 |
03FAh для чтения — регистр идентификации прерывания. Содержит информацию о причине прерывания для обработчика:
биты 7 – 6: 00 — FIFO отсутствует, 11 — FIFO присутствует
бит 3: тайм-аут FIFO приемника
биты 2 – 1: тип произошедшего прерывания:
11 — состояние BREAK или ошибка. Сбрасывается после чтения из 03FDh
10 — пришли данные. Сбрасывается после чтения из 03F8h
01 — буфер передачи пуст. Сбрасывается после записи в 03F8h
00 — изменилось состояние модема. Сбрасывается после чтения из 03FEh
бит 0: 0, если произошло прерывание, 1, если нет
03FAh для записи — регистр управления FIFO (FCR)
биты 7 – 6: порог срабатывания прерывания о приеме данных
00 — 1 байт
01 — 4 байта
10 — 8 байт
11 — 16 байт
бит 2 — очистить FIFO приемника
бит 1 — очистить FIFO передатчика
бит 0 — включить режим работы через FIFO
03FBh для чтения и записи — регистр управления линией (LCR)
бит 7: если 1 — порты 03F8h и 03F9H работают, как делитель частоты порта
бит 6: состояние BREAK — порт непрерывно посылает нули
биты 5 – 3: четность:
? ? 0 — без четности
0 0 1 — контроль на четность
0 1 1 — контроль на нечетность
1 0 1 — фиксированная четность 1
1 1 1 — фиксированная четность 0
? ? 1 — программная (не аппаратная) четность
бит 2: число стоп-бит:
0 — 1 стоп-бит
1 — 2 стоп-бита для 6-, 7-, 8-битных, 1,5 стоп-бита для 5-битных слов
биты 1 – 0: длина слова
00 — 5 бит
01 — 6 бит
10 — 7 бит
11 — 8 бит
03FBH для чтения и записи — регистр управления модемом (MCR)
бит 4: диагностика (выход СОМ-порта замыкается на вход)
бит 3: линия OUT2 — должна быть 1, чтобы работали прерывания
бит 2: линия OUT1 — должна быть 0
бит 1: линия RTS
бит 0: линия DTR
03FCH для чтения — регистр состояния линии (LSR)
бит 6: регистр сдвига передатчика пуст
бит 5: регистр хранения передатчика пуст — можно писать в 03F8h
бит 4: обнаружено состояние BREAK (строка нулей длиннее, чем старт-бит + слово + четность + стоп-бит)
бит 3: ошибка синхронизации (получен нулевой стоп-бит)
бит 2: ошибка четности
бит 1: ошибка переполнения (пришел новый байт, хотя старый не был прочитан из 03F8h, при этом старый байт теряется)
бит 0: данные получены и готовы для чтения из 03F8h
03FDh для чтения — регистр состояния модема (MSR)
бит 7: линия DCD (несущая)
бит 6: линия RI (звонок)
бит 5: линия DSR (данные готовы)
бит 4: линия CTS (разрешение на посылку)
бит 3: изменилось состояние DCD
бит 2: изменилось состояние RI
бит 1: изменилось состояние DSR
бит 0: изменилось состояние CTS
02FFh для чтения и записи — запасной регистр. Не используется контроллером последовательного порта, любая программа может им пользоваться.
Итак, первое, что должна сделать программа, работающая с последовательным портом, — проинициализировать его, выполнив запись в регистр управления линией (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