Программирование игр для Windows. Советы профессионала

Коммуникационная программа: 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

Изучение принципов коммуникации через последовательный порт ввода/вывода похоже на посещение зубного врача — никому не нравится, но всем приходится через это пройти. Мне жаль, что я подвергаю вас подобной пытке, но это исключительно важно знать. Посему не буду вас дольше истязать и перейду к более интересной теме игровых коммуникаций.


Содержание раздела