Коммуникационная программа: NLINK
Программа NLINK завершает наш извилистый путь освоения последовательных коммуникаций для ПК. Я написал эту небольшую коммуникационную программку, чтобы вы могли лучше оценить пройденное. Она соединяет два ПК через СОМ1 или COM2 и позволяет двум игрокам общаться по нуль-модемному кабелю. Для выхода из программы надо нажать клавишу Esc. Листинг 14.4 содержит законченную коммуникационную библиотеку и главную часть программы NLINK.
Листинг 14.4. Коммуникационная программа NLINK (NLINK.C).
// ВКЛЮЧАЕМЫЕ ФАЙЛЫ /////////////////////////////////
#include <dos.h>
#include <bios.h>
#include <stdio.h>
#include <math.h>
#include <conio.h>
#include <graph.h>
// ОПРЕДЕЛЕНИЯ ////////////////////////////////////////////
// регистры UART
#define SER_RBF 0 // буфер чтения
#define SER_THR 0 // буфер записи
#define SER_IER 1 // регистр разрешения прерываний
#define SER_IIR 2 // регистр идентификации прерывания
#define SER_LCR 3 // регистр управляющих данных
// и разрешения загрузки делителя
#define SER_MCR 4 // регистр управления модемом
#define SER_LSR 5 // регистр состояния линии
#define SER_MSR 6 // регистр состояния модема
#define SER_DLL 0 // младший байт делителя
#define SER_DLH 1 // старший байт делителя
// битовые маски для управляющих регистров
#define SER_BAUD_1200 96 // значения делителя
// для скоростей 1200-19200 бод
#define SER_BAUD_2400 48
#define SER_BAUD_9600 12
#define SER_BAUD_19200 6
#define SER_GP02 8 // разрешение прерываний
#define COM_1 0х3F8 // базовый адрес регистров СОМ1
#define COM_2 Ox2F8 // базовый адрес регистров COM2
#define SER_STOP_1 0 //1 стоп-бит на символ
#define SER_STOP_2 4 //2 стоп-бита на символ
#define SER_BITS_5 0 //5 значащих бит на символ
#define SER_BITS 6 1 //6 значащих бит на символ
#define SER_BITS_7 2 //7 значащих бит на символ
#define SER_BITS 8 3 //8 значащих бит на символ
#define SER_PARITY_NONE 0 // нет контроля четности
#define SER_PARITY_ODD 8 // контроль по нечетности
#define SER PARITY EVEN 24 // контроль по четности
#define SER_DIV_LATCH_ON 128 // используется при загрузке делителя
#define PIC_IMR 0х21 // маска для регистра прерываний
#define PIC ICR 0х20 // маска для контроллера
// прерываний (порт 20h)
#define INT_SER_PORT_0 0x0C // маска для управления
// прерываниями СОМ1 и COM3
#define INT_SER_PORT_1 0x0B // маска для управления
// прерываниями COM2 и COM4
#define SERIAL_BUFF_SI2E 128 // размер буфера
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ /////////////////////////////////////////
void ( _interrupt _far *01d_Isr) (); // адрес старой подпрограммы
// обслуживания прерываний
// СОМ-порта
char ser_buffer[SERIAL_BUFF_SIZE];// буфер для приходящих символов
int ser_end = -1,ser_start=-l; // указатели позиции в буфере
int ser_ch, char_ready=0; // текущий символ и флаг
// готовности
int old_int_mask; // старое значение маски
// контроллера прерываний
int open_port; // текущий открытый порт
int serial_lock = ,0; // "семафор" для процедуры
// обработки прерывания,
// управляющий записью
// в программный буфер
////////////////////////////////////////////////////////////
void _interrupt _far Serial_Isr(void)
(
// это процедура обработки прерывания СОМ-порта. Она очень проста.
// При вызове она читает полученный символ из- регистра 0 порта
// и помещает его в буфер программы.
// Примечание: язык Си сам заботится о сохранении, регистров
// и восстановлении состояния
// запрещаем работу всех других функций
//во избежание изменения буфера
serial_lock = 1;
// записываем символ в следующую позицию буфера
ser_ch = _inp(open_port + SER_RBF);
//Устанавливаем новую текущую позицию буфера
if (++ser_end > SERIAL_BUFF_SIZE-1) ser_end =0;
// помещаем символ в буфер
ser_buffer[ser_end] = ser_ch;
++char_ready;
// Восстанавливаем состояние контроллера прерываний
_outp(PIC_ICR,Ox20);
// Разрешаем работу с буфером
serial_lock = 0;
} // конец функции
///////////////////////////////////////////////////////////
int Ready_Serial()
{
// функция возвращает значение, отличное от нуля,
// если есть в буфере есть символы и 0 - в противном случае
return(char_ready);
} // конец функции
///////////////////////////////////////////////////////////
int Serial_Read() {
// функция возвращает последний записанный
//в программный буфер символ
int ch;
// ждем завершения функции обработки прерывания
while(serial_lock){}
// проверяем/ есть ли в символы в буфере
if (ser_end != ser_start)
{
// изменяем значение начальной позиции буфера
if (++ser_start > SERIAL_BUFF_SIZE-1) ser_start = 0;
// читаем символ
ch = ser_buffer[ser_start];
//в буфере стало одним символом меньше
if (char_ready > 0) --char ready;
// возвращаем символ вызвавшей функции
return(ch);
// конец действий, если буфер не пуст
else
// буфер был пуст - возвращаем 0
return(0) ;
}// конец функции /////////////////////////////////
Serial_Write(char ch)
{ // эта функция записывает символ в буфер последовательного порта,
// но вначале она ожидает, пока он освободится.
// Примечание: эта функция не связана с прерываниями-
// и запрещает их на время работы
// ждем освобождения буфера
while(!(_inp(open_port + SER_LSR) 5 0х20)){}
// запрещаем прерывания
_asm cli
// записываем символ в порт
_outp(open_port + SER_THR, ch);
// снова разрешаем прерывания
_asm sti
} // конец функции /////////////////////////////////////
Open_Serial(int port_base, int baud, int configuration)
{
// Функция открывает последовательный порт, устанавливает его
// конфигурацию и разрешает прерывания при получении символа
// запоминаем базовый адрес порта
open_port = port_base;
// сначала устанавливаем скорость работы
// разрешаем загрузку делителя
_outp(port_base + SER_LCR, SER_DIV_LATCH_ON);
// посылаем младший и старший байты делителя
_outp(port_base + SER_DLL, baud);
_outp(port_base + ser_dlh, 0) ;
// устанавливаем конфигурацию порта
_outp(port_base + SER_LCR, configuration);
// разрешаем прерывания
_outp(port_base + SER_MCR, SER_GP02);
_outp(port_base + SER_IER, 1);
// откладываем, работу с контроллером прерываний,
// пока не установим процедуру обработки прерывания
if (port_base == СОМ_1)
{
Old_Isr = _dos_getvect(INT_SER_PORT 0);,
_dos_setvect(INT_SER_PORT_0, Serial_Isr) ;
printf("\n0pening Communications Channel Com Port #1...\n");
}
else
{
Old_Isr = _dos_getvect(INT_SER_PORT_1);
_dos_setvect(INT_SER_PORT_1, Serial_Isr) ;
printf("\n0pening Communications Channel Com Port #2...\n");
}
// разрешаем прерывание СОМ-порта на уровне контроллера прерываний
old_int_mask = _inp(PIC_IMR);
_outp(PIC_lMR, (port_base==COM_l) ? (old_int_mask & OxEF):(old_int_mask & OxF7 ) );
} // конец функции ///////////////////////////////////////////////////////////
Close_Serial (int port_base)
{
// функция закрывает СОМ-порт, запрещает вызов его прерываний
// и восстанавливает старый обработчик прерывания
// запрещаем прерывания по событиям СОМ-порта
_outp(port_base + SER_MCR, 0) ;
_outp(port_base + SER_IER, 0).;
_outp(PIC_IMR, old_int_mask );
// восстанавливаем старый обработчик прерывания
if (port_base == СОМ_1)
{
_dos_setvect(INT_SER_PORT_0, 01d_Isr) ;
printf("\nClosing Conuflunications Channel Corn Port #1.\n");
}
else
{
_dos_setvect(INT_SER_PORT_l, 0ld_Isr);
printf("\nClosing Communications Channel Com Port #2.\n");
}
// конец функции // ОСНОВНАЯ ФУНКЦИЯ /////////////////////////////
main ()
{
char ch;
int done=0;
printf("\nNull Modem Terminal Communications Program.\n\n");
// открываем СОМ1
Open_Serial(COM_1,SER_BAUD_9600,
SER_PARITY_NONE | SER_BITS_8 | SER_STOP_1);
// главный рабочий цикл
while (!done) {
// работа с символами на локальной машине
if (kbhit()) {
// получаем символ с клавиатуры
ch = getch(); printf("%c",ch);
// посылаем символ на удаленную машину
Serial_Write(ch) ;
// не была ли нажата клавиша ESC? Если да - конец работы
if (ch==27) done=l;
// Если был введен символ "перевод каретки" (CR),
// добавляем символ "новая строка" (LF)
if (ch==13)
{
printf("\n");
Serial_Write(10);
}
}// конец обработки клавиатуры
// пытаемся получить символ с удаленной машины
if (ch = Serial_Read()) printf("%c", ch);
if (ch == 27) { printf("\nRemote Machine Closing Connection.");
done=l;
} // конец обработки нажатия ESC на удаленной машине
} // конец цикла while
// закрываем связь и кончаем работу
Close_Serial(COM_l);
} // конец функции main
Изучение принципов коммуникации через последовательный порт ввода/вывода похоже на посещение зубного врача — никому не нравится, но всем приходится через это пройти. Мне жаль, что я подвергаю вас подобной пытке, но это исключительно важно знать. Посему не буду вас дольше истязать и перейду к более интересной теме игровых коммуникаций.