Последовательный порт
Каждый из последовательных портов обменивается данными с процессором через набор портов ввода-вывода: СОМ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
Содержание раздела